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 bare form of an organization's host names no project: it resolves the organization's own baseline snapshot instead, and adds the organization's live operational rules — its Baseline, and any ruleset the source selects with
?ruleset=— see Baseline rules and rulesets. - 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 are refused with a 400 rather than dropped. The binding is the whole answer, which is the point: a developer cannot opt out of production's posture by editing a source string. The one query parameter an organization host does accept is ?ruleset=<name>, on the bare <alias>.compliance.tf/<namespace>/<name>/<provider> form only: it selects a named group of rules the organization published, and can add to the posture but never subtract from it — see Baseline rules and rulesets.
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 saying no compliance baseline is published for your organization | The bare organization form was used and the organization has never published a baseline. The project-environment form does not use the baseline and does not produce this |
A 503 saying the same, with (baseline lookup failed) | The baseline exists but could not be read. Retry |
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 |
Two of these resolve themselves. The build 503 is the common one: 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. The baseline 503 is the other, and it means a read failed rather than a build being slow.
The two baseline answers are the organization host's own fail-closed step, and they are worth reading as a pair. A baseline that cannot be resolved is never replaced by the canonical zero-control posture — that substitution would serve nothing under a manifest that looks legitimate, which is the whole failure this step exists to prevent. Both messages name the organization, and both are returned identically by the version listing and by the download, so a missing baseline fails at the first step of terraform init rather than half way through it.
One qualification on "an organization with no baseline gets a 404". compliance.tf may provision an empty baseline for an organization that never authored one. Such an organization resolves and is served — it does not 404 — but the posture enforces nothing, and the build marks the artifact baseline-not-authored in the manifest's degraded list. Read that reason as "nobody approved this", not as a fault.
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