How a Module Request Resolves
- A module
sourceis the whole integration. On an organization's own host, the three path segments after the hostname name a project, an environment, and a module. - The credential on the request decides on whose behalf it resolves. On an organization's host the caller must be entitled to act for that organization, or the request is refused rather than quietly served at a lower tier.
- The project and environment select exactly one binding. That binding names a snapshot and carries the waiver set frozen into it, and those two together are the effective posture.
- The version list advertises the underlying module's ordinary published versions. Posture is not encoded in the version string — two environments can advertise the same version and receive different modules for it.
- A step that cannot resolve refuses. It does not fall back to a more permissive posture. What it does serve is whatever the binding resolves to — including nothing, if the snapshot enforces nothing.
- Both the version listing and the download count against the daily request budget on metered plans, so one
terraform initspends more than one request.
Why this page exists
Every other page in this section describes something you author: an organization, a project, a config, a snapshot, a binding. This page describes the one moment where all of that becomes a fact on a developer's machine — the terraform init that pulls a module and gets back the posture your organization decided on.
If you are evaluating whether the model fits your estate, this is the page to read. It is the contract your Terraform code binds to.
The address is the contract
compliance.tf serves modules over two different readings of the same three-segment path, and the hostname decides which reading applies.
| Framework or canonical host | Your organization's host | |
|---|---|---|
| Host | soc2.compliance.tf, registry.compliance.tf | <alias>.compliance.tf |
| First path segment | namespace | project slug |
| Second path segment | module name | environment: dev, staging, or prod |
| Third path segment | provider | module token |
| What decides the posture | The hostname you typed, plus any query parameters | The binding on that project environment |
# Framework host: your code names a compliance framework.
module "bucket" {
source = "soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws"
}
# Organization host: your code names your own project and environment.
module "bucket" {
source = "bobthecorp.compliance.tf/payments/prod/s3-bucket"
}The environment segment selects the grammar. It must be exactly dev, staging, or prod — the three environments every project has. That is what tells the registry it is reading a project address rather than an ordinary module path, and it is why a typo in that slot produces a missing-module error rather than a missing-environment one. What it does not do is decide whose posture you receive; the hostname settles that before the path is read at all. Projects and environments owns the grammar of the slug and the module token; organizations and members owns the alias itself; registry endpoints covers the framework-host form and the HTTPS URL variant of both.
What this page owns is what happens after the address is parsed.
The path a request takes
Step by step, in resolution order:
- The hostname is read, and it alone decides on whose behalf the request runs. A first label that is neither a reserved subdomain nor a framework endpoint marks the request as arriving on an organization's host. On such a host every credential must prove it belongs to that organization, whatever grammar the rest of the path uses — the check is derived from the host and never from a path segment, so no URL shape can skip it.
- The credential is resolved. A registry token or a signed-in session identifies who is asking. On an organization's host the request must be backed by an active membership in that organization, or by a token the organization itself owns. See registry tokens for which credential carries which entitlement.
- The project slug and environment select a binding. There is exactly one binding per project environment, and finding it is the only way this path reaches a posture.
- The effective posture is assembled from the snapshot the binding names and the waiver set frozen into that binding. Promotion and gates covers how both got there.
- The module token is resolved to a module identity — the project's own module aliases first, then the published
terraform-aws-modulesnames. - The artifact is served, built to that posture.
Two things worth noticing about the shape of that sequence. The binding is consulted on every request rather than baked into anything Terraform holds, so a promotion takes effect on the next terraform init without anyone reconfiguring a pipeline. And the module token is resolved after the posture, not before — which is why an environment with no binding reports a missing binding even for a module that plainly exists.
What gets served
The download answer is the ordinary Terraform module protocol: a 204 whose X-Terraform-Get header points at a short-lived URL that Terraform then fetches. Nothing about the integration is unusual on the Terraform side — the CLI does not need a plugin, a wrapper, or a custom source type.
Three properties of the answer are worth planning around:
Posture is not in the version string. The version list on an organization's host advertises the underlying module's ordinary published versions. Two environments bound to different snapshots advertise identical version lists and receive different content for the same version. This is deliberate: a version list carrying posture hashes would look like a list of pre-release versions to Terraform, and an unconstrained terraform init would report no versions available at all.
Responses are not cacheable, and are marked as such. Different callers receive different modules for the same URL, so download responses carry no-store cache directives and vary on the credential. Do not place a shared caching proxy or a mirroring layer between your build agents and the registry; it would serve one team's posture to another.
Query parameters do not override a binding. On a framework host, ?enable= and ?disable= let a module source adjust which controls apply — that is the feature described in customize controls. On an organization's host they do not apply. The binding is the whole answer, which is the point: a developer cannot opt out of production's posture by editing a source string.
When a step cannot resolve
Nothing in this path degrades to a more permissive answer. A step that cannot resolve refuses; it never falls back to an unbound module, another environment's posture, or a lower gate. What it does serve is exactly what the binding resolves to, which is a separate question — an environment bound to a snapshot that enforces nothing receives a module that enforces nothing, because that is what the posture says. It is still rebuilt rather than passed through, so the artifact records that the empty posture was applied.
| What Terraform reports | What it means |
|---|---|
A 404 naming an unknown organization alias | The hostname's first label is not an assigned alias |
A 403 refusal | The credential is not entitled to act for that organization |
A 404 naming no binding or snapshot for the project and environment | The address parsed, but that environment has nothing bound — or the slug is subtly wrong |
A 404 naming a missing module | The module token resolved to nothing, or to a module with no published version |
A 429 with Retry-After | The daily request budget is spent — see plans and entitlements |
A 503 asking you to retry shortly | A posture nobody has fetched before is being built; re-run terraform init |
The 503 is the only one of these that resolves itself. The first request for a posture that has never been served has to build the module, and while that is happening other requests for the same posture wait rather than building duplicates. A request that waits too long is told to come back instead of being held past the point where Terraform would give up on it.
Behind the gate
The rest of this page is the operator's half: which kind of credential is valid against which host form and what each one yields, exactly where each resolution step fails closed and where one deliberately does not, how the effective posture is assembled from a snapshot and a frozen waiver set, what a refusal carries in its body and headers, the version-string trap on an organization's host, and the fallback rules for a module that compliance.tf has never published.
Related
- Projects and environments — the slug, the three environments, and how a module token resolves
- Organizations and members — the alias that fronts every registry URL, and what membership does
- Promotion and gates — how a binding and its frozen waiver set came to be
- Registry tokens — the credential a
terraform initpresents - Plans and entitlements — the daily request budget and what a plan gates
- Registry endpoints — the framework-host form and the HTTPS URL variant