ADR 028 — OpenTofu cutover via binary indirection
- Status: Accepted (2026-07-16) — records the Phase 3.13 decision from the unified infrastructure control plane plan; supersedes the deferral in ADR 026 §D2
- Authors: Platform ops
- Related: ADR 004,
ADR 026,
Unified Infrastructure Control Plane Plan §8 (private
alphaswarm_internalplanning repo), Central Deployment Control — Next Steps
Context
The estate's IaC substrate is HashiCorp Terraform, pinned at v1.10 (1.10.5 in
alphaswarm_platform's setup-terraform action, 1.10.0 in the monolith's
Terraform workflows, hashicorp/terraform:1.10 as the controller's default
image) and invoked solely through TerraformRuntime/TerraformExecutor (hard
rule 42, the "One Gate"). Terraform is licensed under the BSL-1.1, which the codified license
policy prohibits for new dependencies. OpenTofu is the MPL-2.0,
Linux-Foundation-governed fork accepted into the CNCF (Sandbox, April 2025),
API- and provider-compatible with Terraform, and its decisive multi-tenant
differentiator is native state and plan encryption (GA since v1.7; PBKDF2
and AWS-KMS key providers) — which the platform wants for per-cell encrypted
state.
Two facts make a big-bang swap risky and unnecessary:
- Grandfathered runtime. The shipped
terraformbinary predates the license gate; it is an explicit allowlist entry, not a new dependency. - HCP-backed edge stacks. The production public-edge stacks use the HCP (Terraform Cloud) backend. Migrating those is the hard tail of any cutover and is not gated by the same encryption requirement (they are already remote-state-managed).
ADR 026 §D2 deferred the cutover decision to this ADR after a dev/qa dual-binary trial.
Decision
Migrate to OpenTofu by binary indirection, not by a big-bang cutover.
- Binary resolution is tofu-first.
TerraformExecutorresolves the IaC binary viaALPHASWARM_CP_TERRAFORM_BINARY_PATH/asctl_iac_tofu_preferred(resolvetofu, fall back toterraform).-jsonstreaming andplan -detailed-exitcodedrift detection behave identically on both binaries, so the One Gate, drift scans, and audit are unaffected by which binary runs. - State encryption renders only under tofu. The OpenTofu
encryption{}state block (AWS-KMS key provider, per-cell key) is emitted only when the resolved binary istofu. On a terraform host the block is absent and state is unencrypted-at-CLI as before — no partial/incompatible state files. The renderer (asctl/state_encryption.py::with_state_encryption, gated byasctl_state_encryption_enabled) is implemented and unit-tested, but as of this writing has no production caller in the spec-builder/provisioning path — no cell has state encryption enabled yet; this lands with the tofu rollout in point 4. - License gate stays on new dependencies.
check_license_policy.pyblocks any NEW BSL/SSPL dependency (including newhashicorp/vault-style provider pins and terraform additions outside the allowlist). The grandfatheredterraformbinary remains an allowlisted entry with a review date, retired as cells migrate to tofu. - Rollout order. (a) Run tofu-preferred in dev/qa first, validating
state-encryption round-trips (encrypt → destroy state → restore with the KMS
key) and
-json/exit-code parity against the terraform baseline. (b) Migrate shared-cell and app-tier stacks. (c) HCP-backed edge stacks migrate last, or are carved out permanently if the HCP backend is retained for them — this is re-evaluated at the review date, not forced now. - No dual-write of state. A given cell's state is owned by exactly one
binary at a time; migration flips the binary for that cell's workspace after
a validated
planshows no diff, following the expand-migrate-contract discipline used elsewhere.
Consequences
- The platform gains per-cell encrypted state (the multi-tenant driver) without a risky cutover; encryption arrives cell-by-cell as each flips to tofu.
- The One Gate, drift scans, PromotionRequest flow, and audit are unchanged — they operate on the resolved binary transparently.
- The license posture is satisfied: no new BSL dependency is introduced, and the grandfathered terraform binary has an explicit retirement path.
- The HCP edge-stack tail is scoped and deferred rather than blocking the whole migration; a follow-up decision at the review date either migrates or permanently carves them out.
- Operational cost: hosts that run apply must have the
tofubinary installed for encryption to take effect; until then those cells run terraform-compatible and unencrypted-at-CLI (safe, just not yet encrypted).
Alternatives considered
- Big-bang cutover with an immediate BSL ban. Rejected — the shipped
terraform v1.15 path, committed
hashicorplockfiles, and HCP-backed edge stacks make a simultaneous swap far riskier than gated binary indirection. - Stay on Terraform permanently. Rejected — BSL conflicts with the codified permissive-license policy for the trajectory of the platform, and Terraform's open CLI has no native state-encryption equivalent, which is the multi-tenant requirement.
- Pulumi as the primary IaC engine. Rejected in ADR 026 (ESC requires the Pulumi Cloud backend; Automation API in-process threading caveats); retained only as a secondary/interop option.
- A second parallel
tofusubprocess path alongside the terraform executor. Rejected — hard rule 42 requires a single sanctioned executor; indirection lives insideTerraformExecutor, not beside it.