Terraform provider and the HCL view

Phase B adds a third way to author a flow, HCL, and a Terraform provider that applies it. This page is the reader's map: what the provider is, what a .flow.tf looks like, how it pairs with a document, which versions work together, how to migrate from hashicorp/aws, and how the provider is published. The contract itself is conformance/hcl/README.md; the decisions are ADR-0006 and ADR-0007. To learn it by doing, start with Your first flow with the Terraform provider.

What it is

flow-as-code/flowascode is a Terraform provider (Terraform 1.8 and later, OpenTofu 1.10 and later) whose resources take a flow's actions as HCL blocks and create, update and delete the flow through the Amazon Connect API:

It is not the emitter. flow-cli emit --target tf still writes aws_connect_contact_flow resources over rendered content for anyone who stays on hashicorp/aws; --target flowascode writes the provider's resources. Pick tf when the flows are generated artifacts nobody edits as HCL; pick flowascode when the flows are authored in HCL, reviewed as HCL, or need plan-time lint.

--target flowascode writes flows.tf, outputs.tf, variables.tf and versions.tf.example for a set of documents, never a per-document <name>.flow.tf, so an emit into a directory the studio serves cannot create a companion. outputs.tf carries, per document and named by its FlowDoc name, its arn and sha256() of its flowdoc, the document with its references still tokens: the hash a promotion gate compares between environments (docs/tutorials/02-promote.md), equal when the documents are and known at plan time, so no root writes its own. A module gets a version and an alias resource per alias a flow in the set invokes it through (rule 27); a module nothing in the set references gets its resource alone, and flow-cli emit --module-alias module:greeting@live declares an alias it publishes anyway, in the same shape, so a module released in a root of its own is bound from another root through the address map and the alias ARN outputs.tf carries (packages/cli/README.md, "emit"). Each document is one resource; its refs map binds each key to the address the address map gives, to a resource the set emits itself (a flow, a module, or a module invoked by alias through the version and alias resources the emitter writes beside it), or to null under a # TODO comment, which the provider refuses at plan time naming the key. Because that null validates, flow-cli emit --target flowascode refuses the run on any unbound reference, listing each key and its documents, unless --allow-unbound asks for the partial map; a map key no reference uses is a warning, or an error under --strict (packages/cli/README.md, "emit"). The emitted versions.tf.example asks for the provider at ~> 0.1, the range the tutorials and example roots ask for, held to one string by a test. The goldens are conformance/hcl/emit/, one case per emit-tf case.

The resource shape

resource "flowascode_contact_flow" "appointment_line" {
          instance_id = var.connect_instance_id
          name        = "appointment-line"
          type        = "CONTACT_FLOW"

          refs = {
            "hours:main-line"    = aws_connect_hours_of_operation.main_line.arn
            "queue:appointments" = aws_connect_queue.appointments.arn
          }

          action {
            id   = "welcome"
            next = "check-hours"
            message_participant {
              text = "Thanks for calling."
            }
            error {
              type = "NoMatchingError"
              next = "hang-up"
            }
          }

          action {
            id = "hang-up"
            disconnect_participant {}
          }
        }
        

One action block per action, in order. Inside it: id, next, one typed sub-block named after the action type (message_participant, check_hours_of_operation, and so on, the catalog's block name) or a generic block for a type the tooling does not model, then condition and error blocks, then position where the action sits somewhere other than where auto-layout puts it. Reference fields hold a key ("queue:front-desk") that the refs map binds to a Terraform expression; a literal ARN is refused at plan time. conformance/hcl/README.md has every rule, and its goldens are the byte-exact examples.

Companions

A document has one companion source, <name>.flow.ts or <name>.flow.tf, paired by suffix and chosen when the document is created. The studio and the bridge treat the two alike: a save regenerates the companion, an edit to the companion re-syncs the canvas, and comments marked @keep survive either way. What a .flow.tf carries beyond the document (its refs bindings, instance_id, tags, state and lint settings) is kept across regeneration. flow-cli convert --to ts|tf switches a document's companion and says what it drops.

The commands that write or read a companion take either kind: flow-cli codegen <doc> --to tf writes <name>.flow.tf (without --to, the kind the document's meta.sourceKind names), flow-cli synth <name>.flow.tf reads one back in process, and flow-cli export --author tf writes one per exported document. flow-cli init --author tf scaffolds a document with a .flow.tf, and flow-cli studio watches .flow.tf companions as it watches .flow.ts ones: an edit re-syncs the canvas within a second, a canvas save regenerates the file keeping what it carries, and a name with both companions is refused until one goes. In the studio the toolbar badges the open document's companion, New flow picks the companion for a new document, and a .flow.tf's lint block reaches the lint panel, and "Export as…" offers "Terraform (flowascode provider)", the same bytes as emit --target flowascode. packages/cli/README.md has the details.

Versions and compatibility

The provider has its own semantic version from v0.1.0 and records the commit of conformance/ it vendors. The FlowDoc format version it reads is the one the schema under conformance/schema/ names at that commit.

Provider FlowDoc Schema file Terraform OpenTofu
0.1.x 0.2 flowdoc-0.2.schema.json 1.8 and later 1.10 and later
none released; D10 0.3 flowdoc-0.3.schema.json 1.8 and later 1.10 and later

A repository test holds the table's FlowDoc column to FLOWDOC_VERSION and its schema file to one present under conformance/schema/. The 0.3 row's provider column is filled in by tasks/D10 with the version read from the registries once that release is out; until then a published provider reads 0.2, and an emitted tree carries no FlowDoc version, so the provider refuses only a typed sub-block or a refs key type its vendored catalog lacks. An older reader of a document (npm 0.2.x, provider 0.1.x) refuses a 0.3 file by its version, and treats a 0.3 token in a file stamped 0.2 as malformed: the check is the reader's token pattern, not the frozen 0.2 schema, which applies the pattern only at refs[].token and the typed reference positions.

Migrating from hashicorp/aws

The provider's guide migrate-from-aws-provider (in its registry documentation) has the steps. Two ways in, neither recreating the flow:

A flow or module whose Connect name is not a slug (a console's "Main Line") keeps it: name becomes the slug and display_name the Connect name, so adopting it renames nothing.

Publishing and the acceptance gate

Releases are tagged v* in the provider repository. The release workflow checks the tag is on main, the changelog has the version, and the unit and conformance lane passes, with no credentials; goreleaser then builds and signs SHA256SUMS in a GitHub Environment whose GPG key and required reviewer mean a tag alone cannot publish. The signing key's fingerprint is in the provider repository's SECURITY.md, and its public half is signing-key.asc there. Every pull request also runs live acceptance tests against a sandbox Connect instance on both floors (Terraform 1.8, OpenTofu 1.10), and an unreachable sandbox blocks the merge.

v0.1.0 was published on 2026-09-29. The Terraform Registry serves it at https://registry.terraform.io/providers/flow-as-code/flowascode, and the OpenTofu registry has listed it since 2026-09-30; terraform init and tofu init both install it with its signature checked. This repository's emit-tf job validates the provider-shaped output against the published provider on OpenTofu 1.10 and a current release (task B03e).

Considered and not used

Provider-defined functions, a provider::flowascode::flowdoc() that would turn HCL into a document at plan time, were considered as a way to keep hashicorp/aws as the only resource provider. They were not used: the document would still need API calls only a managed resource can make, and a function cannot carry the plan-time diagnostics the lint rules produce.