IaC Module Specification Template (OpenTofu, Terraform)
One reusable IaC module: inputs and outputs, guardrails, security baseline, tagging contract, SemVer rules, tests and policy as code, deprecation
Cách dùng: Use it when you build or own a reusable OpenTofu or Terraform module that other teams call. Fill the scope, interface principles and inputs (sections 1 to 3) first and review them with consumer teams before coding. The main pitfall is refactoring resource addresses or changing defaults in a minor release, which replaces or alters infrastructure for every caller.
Xem trước
IaC Module Specification: [Module name]
Specification for one reusable infrastructure-as-code module: purpose and scope, interface design, inputs and outputs, defaults and guardrails, security baseline, tagging contract, provider and tool compatibility, state and lifecycle rules, versioning with Semantic Versioning 2.0.0, tests including policy as code, usage examples, release, support and deprecation. Written for HCL modules used with OpenTofu or Terraform, with module structure aligned with the OpenTofu standard module structure and the native
testcommand. Replace every[bracketed]value and every row marked Example. The worked example is a private object storage bucket module at Corvid Commerce, a fictional SaaS company.
Document control
| Field | Value |
|---|---|
| Document ID | [IAC-SPEC-014] |
| Module name | [object-bucket] |
| Registry address | [registry.example.com/namespace/name/provider] |
| Source repository | [git URL] |
| Module version described | [2.3.0] |
| Module status | [Experimental / Supported / Deprecated / Retired] |
| Owning team | [@platform-infrastructure] |
| Version | [1.0] |
| Status | [Draft / In review / Approved] |
| Owner | [@module-maintainer] |
| Approvers/reviewers | [@platform-lead], [@cloud-security-architect], [@finops-lead], [@consumer-team-rep] |
| Last updated | YYYY-MM-DD |
| Classification | [Internal] |
Approvals
| Role | Name | Decision | Date |
|---|---|---|---|
| Platform lead | [@name] | [Approve / Approve with changes / Reject] | YYYY-MM-DD |
| Cloud security architect | [@name] | [Decision] | YYYY-MM-DD |
| FinOps lead (tagging contract) | [@name] | [Decision] | YYYY-MM-DD |
| Consumer team representative | [@name] | [Decision] | YYYY-MM-DD |
Revision history
| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | First draft for module 1.0.0 |
| 1.0 | YYYY-MM-DD | [@name] | Example: Approved; module 2.0.0 enforces encryption with customer-managed keys |
| 1.1 | YYYY-MM-DD | [@name] | Example: Module 2.3.0 adds lifecycle_rules input |
Contents
1. Purpose and scope
1.1 Purpose
[One paragraph: the capability this module provides and why a shared module is better than each team writing its own resources.]
Example: The object-bucket module creates a private object storage bucket that meets Corvid Commerce's security and cost allocation baseline by default: encrypted with a customer-managed key, public access blocked, versioning on, access logged, and tagged per the tagging standard. Product teams use it for application assets, exports and backups. Without it, teams copied resource blocks between repositories and drifted from the baseline (12 of 41 buckets failed the posture review in 2026-Q1).
1.2 Scope
| In scope | Out of scope |
|---|---|
| Example: One bucket with its encryption, versioning, public access block, ownership controls, lifecycle rules and access logging target | The KMS key itself (created by the kms-key module and passed in) |
| Example: A bucket policy that enforces TLS and denies uploads that request a different key | Cross-account access grants (use the bucket-access-grant module) |
| Example: Optional replication configuration to a second region | Static website hosting and public buckets (not supported by design) |
| [Item] | [Item] |
1.3 Consumers and placement
| Consumer | Use | Called from |
|---|---|---|
| Example: Product teams on the golden path "Java HTTP service" | Application asset storage | Service infrastructure repository |
| Example: Data platform team | Export landing buckets | Data platform stack |
| Example: Backup service | Backup vault buckets with object lock | Backup stack, through the backup-vault module |
| [Consumer] | [Use] | [Repository or stack] |
2. Interface design principles
The module interface follows these rules:
- IF-01 Expose only inputs that callers legitimately need to vary. Security-critical settings are not inputs (section 5).
- IF-02 Use typed inputs with
objectandoptional()attributes for grouped settings instead of many loose variables. - IF-03 Every input and output has a one- or two-sentence description; the README input and output tables are generated from them.
- IF-04 Validate inputs in the module with
validationblocks and clear error messages, so mistakes fail at plan time. - IF-05 Do not configure providers inside the module. Callers pass providers, including aliased providers for a second region.
- IF-06 Outputs expose identifiers and attributes that other modules need; never output secrets, and mark sensitive outputs.
- IF-07 Prefer composition: small modules that callers combine over deep chains of modules calling modules.
- IF-08 Naming: inputs and outputs use
snake_case; booleans are phrased positively (enable_replication, notdisable_replication).
2.1 Repository layout
The layout follows the standard module structure: README, main, variables and outputs files at the root, with examples and tests in their own folders.
object-bucket/
README.md # usage, generated input and output tables
SPEC.md # this specification
CHANGELOG.md # Keep a Changelog format
LICENSE
main.tf # resources
variables.tf # inputs (section 3)
outputs.tf # outputs (section 4)
versions.tf # required_version and required_providers (section 8)
policy.tf # bucket policy document
examples/minimal/ # smallest valid call (section 12)
examples/replicated/ # cross-region replication
tests/ # *.tftest.hcl files (section 11)
policy/tags.rego # policy as code (section 11.3)
3. Inputs
| ID | Input | Type | Required | Default | Validation | Description |
|---|---|---|---|---|---|---|
| IN-01 (Example) | name | string | Yes | None | 3 to 50 characters, lower case letters, digits and hyphens | Logical name; the module adds environment and a suffix to form the bucket name |
| IN-02 (Example) | environment | string | Yes | None | One of dev, staging, prod | Deployment environment; also used as a tag value |
| IN-03 (Example) | kms_key_arn | string | Yes | None | Must be a key ARN, not an alias | Customer-managed key for server-side encryption |
| IN-04 (Example) | tags | map(string) | Yes | None | Must contain the mandatory keys in section 7 | Caller tags, merged with module tags |
| IN-05 (Example) | log_bucket_id | string | Yes | None | Non-empty | Target bucket for server access logs |
| IN-06 (Example) | lifecycle_rules | list(object({...})) | No | [] | Expiration days at least 1; transition days lower than expiration | Object expiry and storage class transitions |
| IN-07 (Example) | enable_replication | bool | No | false | Requires IN-08 when true | Replicate objects to a second region |
| IN-08 (Example) | replication | object({ destination_bucket_arn = string, kms_key_arn = string }) | No | null | Both attributes set when IN-07 is true | Replication target |
| IN-09 (Example) | force_destroy | bool | No | false | Must be false when environment = "prod" | Allow deletion of a non-empty bucket (test environments only) |
| [IN-NN] | [input] | [type] | [Yes / No] | [default] | [rule] | [description] |
Example of an input with validation, valid for both tools:
variable "environment" {
description = "Deployment environment. Used in the bucket name and as the environment tag."
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment must be one of: dev, staging, prod."
}
}
Cross-variable rules (IN-09 depends on IN-02) are enforced with a precondition on the bucket resource, which keeps the rule next to the resource it protects and works on every supported tool version.
4. Outputs
| ID | Output | Type | Sensitive | Description | Typical consumer |
|---|---|---|---|---|---|
| OUT-01 (Example) | bucket_id | string | No | Bucket name | Application configuration |
| OUT-02 (Example) | bucket_arn | string | No | Bucket ARN | IAM policies, bucket-access-grant module |
| OUT-03 (Example) | bucket_regional_domain_name | string | No | Regional endpoint name | Content delivery configuration |
| OUT-04 (Example) | replication_role_arn | string | No | Role used for replication, null when disabled | Audit and access reviews |
| [OUT-NN] | [output] | [type] | [Yes / No] | [description] | [consumer] |
Removing an output or changing its type is a breaking change (section 10).
5. Defaults and guardrails
| ID | Setting | Behaviour | Can the caller change it? | Enforced by |
|---|---|---|---|---|
| GR-01 (Example) | Public access block | All four public access settings on | No | Hard-coded in the module; organisation policy as second layer |
| GR-02 (Example) | Encryption at rest | Customer-managed key from IN-03; bucket key enabled | Key: yes; turning encryption off: no | Hard-coded default SSE-KMS with the IN-03 key; bucket policy denies uploads that request a different key or SSE-S3 |
| GR-03 (Example) | TLS in transit | Bucket policy denies requests without secure transport | No | Bucket policy |
| GR-04 (Example) | Versioning | Enabled | No | Hard-coded |
| GR-05 (Example) | Object ownership | Bucket owner enforced (access control lists disabled) | No | Hard-coded |
| GR-06 (Example) | Access logging | Server access logs to IN-05 | Target: yes; disabling: no | Required input |
| GR-07 (Example) | Deletion protection | force_destroy = false enforced in prod; deletion of prod buckets also denied by an organisation policy (deny DeleteBucket outside the break-glass role) | force_destroy only outside prod | Precondition (IN-09); organisation policy; POL-05 on the plan |
| [GR-NN] | [Setting] | [Behaviour] | [Yes / No] | [Mechanism] |
Defence in depth: module guardrails make the right thing easy; landing zone controls (service control policies, organisation policies and detective rules in the Landing Zone Design) catch resources created outside the module. Do not rely on either layer alone.
Exceptions: a team that needs a setting the module forbids raises an exception request with the reason, the compensating control, an expiry date and approval from the cloud security architect. Approved exceptions are listed here:
| Exception ID | Team | Guardrail | Reason | Compensating control | Expiry | Approved by |
|---|---|---|---|---|---|---|
| EXC-01 (Example) | Data platform | GR-05 | A legacy partner integration still writes objects with access control lists | Partner limited to one prefix; access reviewed monthly; migration to a bucket policy grant planned | 2027-03-31 | [@cloud-security-architect] |
| [EXC-NN] | [Team] | [GR ID] | [Reason] | [Control] | YYYY-MM-DD | [@approver] |
6. Security baseline
| ID | Requirement | Threat addressed | Control framework reference | Implemented by | Verified by |
|---|---|---|---|---|---|
| SEC-01 (Example) | No public read or write | Data exposure | [Internal control ID] | GR-01 | TST-03, POL-02 |
| SEC-02 (Example) | Encryption at rest with customer-managed key | Disclosure of stored data; key misuse | [Control ID] | GR-02 | TST-02, POL-03 |
| SEC-03 (Example) | Encryption in transit enforced | Interception | [Control ID] | GR-03 | TST-04 |
| SEC-04 (Example) | Recoverability of overwritten or deleted objects | Ransomware, operator error | [Control ID] | GR-04, GR-07 | TST-02, POL-05 |
| SEC-05 (Example) | Access is logged | Undetected misuse | [Control ID] | GR-06 | TST-05 |
| SEC-06 (Example) | Least privilege replication role | Privilege escalation | [Control ID] | Role scoped to source and destination buckets and keys | TST-06 |
| [SEC-NN] | [Requirement] | [Threat] | [Reference] | [GR ID] | [TST or POL ID] |
Caller responsibilities that the module cannot enforce: who receives read and write access (use least privilege grants), the classification of data stored, key rotation and key policy (owned by the key module), and secrets handling in applications (see Secrets Management Standard, security/secrets-management-standard). For the threat analysis behind this table, see the Threat Model (security/threat-model).
7. Tagging contract
The Cost Allocation and Tagging Standard (ops/cost-allocation-tagging-standard) defines the mandatory keys and allowed values. This module implements that standard; if they disagree, the standard wins. The module is fixed in the next release, which is MAJOR when it rejects tag sets that were valid before (section 10).
| Tag key (from the standard's tag dictionary) | Source | Rule in this module |
|---|---|---|
cost-center | Caller (IN-04) | Required; validated against the pattern in the standard (Example: cc- and 4 digits) |
owner-team | Caller (IN-04) | Required; team slug from the team directory, not a person |
service | Caller (IN-04) | Required; service ID from the service catalogue |
environment | Module, from IN-02 | Set by the module; a caller value is overwritten so the tag cannot disagree with the input. The module accepts dev, staging and prod, a subset of the standard's values |
data-classification | Caller (IN-04) | Required, because a bucket is a data store |
managed-by | Module | Fixed value iac |
Keys and values follow the standard's format rules (lower case letters, digits and hyphens), so the module does not add its version as a tag; the version is pinned in the caller's module block and found through usage tracking (13.2).
Contract rules:
- TAG-01 Every taggable resource the module creates MUST carry the merged tag set, including roles and replication resources.
- TAG-02 Merge order: provider default tags, then caller tags, then module-owned tags. Module-owned keys always win.
- TAG-03 A missing mandatory key MUST fail at plan time (input validation) and again in the policy check (POL-01).
- TAG-04 Resources that cannot be tagged are listed in the README so the FinOps team can allocate them by rule.
The periodic Cloud Cost Review (ops/finops-cost-review) reports tag compliance; resources created by this module SHOULD show 100%.
8. Compatibility and dependencies
| Dependency | Constraint | Tested versions (Example, checked 2026-10-24) | Notes |
|---|---|---|---|
| OpenTofu | >= 1.8.0 | 1.13.2 and the oldest supported minor | .tofu files are not used, so the module stays readable by both tools |
| Terraform | >= 1.8.0 | 1.17.0 and the oldest supported minor | Native tests need 1.6 or later; provider mocking needs 1.7 or later |
| Cloud provider plugin | >= 6.0, < 7.0 | 6.70.0 | A new provider major version is adopted in a module major release |
| Other modules | None called | Composition happens in the caller |
terraform {
required_version = ">= 1.8.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.0, < 7.0"
configuration_aliases = [aws.replica]
}
}
}
configuration_aliases declares that the module expects an aliased provider aws.replica for the replica region (IF-05). Every caller must then pass it, even with replication off (pass the default provider twice). If that burden is too high for most callers, split replication into its own module instead.
Support rule (Example): test against the latest release and the oldest supported minor of each tool; raise the minimum only in a minor release with a changelog note, unless it forces callers to upgrade a tool major version, which is a major release. Features that exist in only one tool (for example OpenTofu state encryption, OpenTofu 1.12 prevent_destroy expressions, or features added to Terraform after the fork) are not used inside the module; callers may use them in their root configuration.
9. State, lifecycle and drift
- LC-01 Resource addresses inside the module are part of the contract with every caller's state. Renaming or moving a resource MUST include a
movedblock, kept for at least one major version. - LC-02 Changes that force replacement of a stateful resource are breaking changes and need a migration guide.
- LC-03 The module does not set
prevent_destroy. In Terraform everylifecycleargument must be a literal inside the resource block, so a caller cannot set it on a resource in a called module, and a literaltruewould also block deletion in test environments. OpenTofu 1.12.0 and later accept a reference to an input variable, but the module stays tool-neutral (section 8). Production protection comes from GR-07 (noforce_destroyin prod, an organisation policy that denies bucket deletion, POL-05 on every plan) and versioning (GR-04). A team that needsprevent_destroyuses a separateobject-bucket-protectedvariant that sets it totrue, published from the same repository and test suite. - LC-04 Adopting existing resources uses
importblocks in the caller's root module, documented in the README migration section. - LC-05 Drift detection runs a scheduled plan per consuming stack (Example: daily); any drift on a guardrail attribute raises a security finding, other drift a ticket to the owning team.
Example: when 2.0.0 removed count from the bucket, a moved block from aws_s3_bucket.this[0] to aws_s3_bucket.this kept every caller's bucket in place.
10. Versioning and changelog
The module follows Semantic Versioning 2.0.0: MAJOR for incompatible changes, MINOR for backward-compatible additions, PATCH for backward-compatible fixes. Versions 0.y.z are for initial development and are not used in production stacks. The organisation's wider rules are in the Versioning and Release Policy (dev/versioning-release-policy).
| Change | Release type | Reason |
|---|---|---|
| Remove or rename an input or output | MAJOR | Callers must change code |
| Make an optional input required | MAJOR | Existing calls fail |
| Change a default that alters created resources | MAJOR | Infrastructure changes without caller action |
| Tighten a validation so that previously valid values fail | MAJOR | Existing calls may fail |
| Change that forces replacement of a stateful resource | MAJOR | Data loss risk; needs migration guide |
| Raise the provider constraint to a new major version | MAJOR | Callers must upgrade the provider |
| Add an optional input with a default that changes nothing | MINOR | Backward compatible |
| Add an output | MINOR | Backward compatible |
| Add a new guardrail that only affects new resources and is required by the security baseline | MINOR with notice, or MAJOR if existing resources change | Decide per case; record the decision |
| Fix a bug, or change only documentation, without changing the interface or intended behaviour | PATCH | Backward compatible |
Changelog: CHANGELOG.md in Keep a Changelog format, one section per release with Added, Changed, Deprecated, Removed, Fixed and Security headings. Commit messages MAY follow Conventional Commits to automate release notes.
| Version | Date | Type | Summary |
|---|---|---|---|
| 2.0.0 (Example) | 2026-03-02 | MAJOR | kms_key_arn and data-classification tag required; default provider-managed encryption removed |
| 2.1.0 (Example) | 2026-05-11 | MINOR | Added optional replication (IN-07, IN-08) |
| 2.2.0 (Example) | 2026-07-20 | MINOR | Added module-owned managed-by tag (data-classification has been required since 2.0.0) |
| 2.3.0 (Example) | 2026-09-14 | MINOR | Added lifecycle_rules input (IN-06) |
| [x.y.z] | YYYY-MM-DD | [Type] | [Summary] |
Callers pin with a pessimistic constraint on the minor version, for example version = "~> 2.3", which allows 2.3 and later 2.x releases but not 3.0.
11. Testing
11.1 Test layers
| Layer | Tool | What it proves | Runs | Gate |
|---|---|---|---|---|
| Format and validate | tofu fmt -check, tofu validate (or the Terraform equivalents) | Syntax and internal consistency | Every commit | Blocking |
| Lint and static security scan | [Linter], [IaC security scanner] | Provider-specific mistakes, known misconfigurations | Every commit | Blocking on high findings |
| Unit tests (plan) | Native test command, command = plan, mock providers where needed | Inputs validate; guardrails present; tags merged | Every pull request | Blocking |
| Policy as code | Open Policy Agent with Conftest on the plan JSON | Organisation rules hold for the planned resources | Every pull request, and in every consuming pipeline | Blocking |
| Integration tests (apply) | Native test command, command = apply, in a sandbox account | Resources are created as specified and are destroyed afterwards | Before each release, nightly | Blocking for release |
| Example tests | Plan of each folder in examples/ | Examples stay valid | Every pull request | Blocking |
11.2 Test cases
| ID | Test | Type | Expected result |
|---|---|---|---|
| TST-01 (Example) | Invalid environment value | Unit, expect_failures on the variable | Plan fails with the validation message |
| TST-02 (Example) | Default call | Unit | Encryption uses IN-03 key; versioning enabled |
| TST-03 (Example) | Default call | Unit | All four public access block settings true |
| TST-04 (Example) | Bucket policy | Unit | Policy contains a deny for non-TLS requests |
| TST-05 (Example) | Logging | Unit | Logging target equals IN-05 |
| TST-06 (Example) | Replication enabled | Integration | Object written to source appears in destination, encrypted with destination key |
| TST-07 (Example) | force_destroy = true with environment = "prod" | Unit, expect_failures on the bucket resource | Plan fails with the precondition message |
| TST-08 (Example) | Tags | Unit | Every taggable resource has the mandatory keys; environment equals IN-02 |
| [TST-NN] | [Test] | [Type] | [Expected] |
Example unit test file (tests/defaults.tftest.hcl), runnable with tofu test or terraform test:
mock_provider "aws" {}
mock_provider "aws" {
alias = "replica"
}
variables {
name = "assets"
environment = "dev"
kms_key_arn = "arn:aws:kms:eu-west-1:111122223333:key/00000000-0000-0000-0000-000000000000"
log_bucket_id = "corvid-dev-access-logs"
tags = { cost-center = "cc-1042", owner-team = "kestrel", service = "checkout-api", data-classification = "internal" }
}
run "public_access_is_blocked" {
command = plan
assert {
condition = alltrue([
aws_s3_bucket_public_access_block.this.block_public_acls,
aws_s3_bucket_public_access_block.this.block_public_policy,
aws_s3_bucket_public_access_block.this.ignore_public_acls,
aws_s3_bucket_public_access_block.this.restrict_public_buckets,
])
error_message = "Public access block must be fully enabled."
}
}
run "prod_rejects_force_destroy" {
command = plan
variables {
environment = "prod"
force_destroy = true
}
expect_failures = [aws_s3_bucket.this]
}
11.3 Policy as code
Policies run against the JSON form of a saved plan (tofu show -json plan.out or terraform show -json plan.out) in this module's pipeline and in every consuming pipeline, so they also catch resources created outside the module.
| ID | Policy | Severity | Source rule |
|---|---|---|---|
| POL-01 (Example) | Mandatory tag keys present on every taggable resource | Deny | Tagging standard |
| POL-02 (Example) | No bucket without a full public access block | Deny | SEC-01 |
| POL-03 (Example) | No bucket encrypted only with a provider-managed key in prod | Deny | SEC-02 |
| POL-04 (Example) | Module source pinned to a version, not a branch | Warn | Section 10 |
| POL-05 (Example) | No delete action on a bucket in a prod plan without an approved change record | Deny | GR-07 |
| [POL-NN] | [Policy] | [Deny / Warn] | [Reference] |
Example policy POL-01 in Rego (OPA 1.x syntax). It reads tags_all, which includes provider default tags, covers only resource types listed as taggable, and also requires data-classification on data stores (section 7). If a tag value is unknown until apply, the rule reports it as missing; resolve such cases with known values in the plan:
package terraform.tags
universal := {"cost-center", "owner-team", "service", "environment"}
taggable := {"aws_s3_bucket", "aws_iam_role"}
data_store := {"aws_s3_bucket"}
required_for(t) := universal | {"data-classification"} if {
t in data_store
} else := universal
deny contains msg if {
rc := input.resource_changes[_]
rc.mode == "managed"
rc.type in taggable
some action in rc.change.actions
action in {"create", "update"}
tags := object.get(rc.change.after, "tags_all", {})
missing := required_for(rc.type) - {k | tags[k]}
count(missing) > 0
msg := sprintf("%s is missing required tags: %v", [rc.address, missing])
}
11.4 Acceptance criteria for a release
- All blocking layers in 11.1 pass on the release commit.
- Every guardrail in section 5 and every requirement in section 6 has at least one passing test or policy.
- Integration tests leave no resources behind (sandbox inventory checked after the run).
- The changelog entry and the release type match the table in section 10, confirmed by a second maintainer.
- For a major release, a migration guide exists and was tested on one real consuming stack.
12. Usage examples
Minimal call (examples/minimal):
module "assets_bucket" {
source = "registry.corvid.example/platform/object-bucket/aws"
version = "~> 2.3"
providers = { aws = aws, aws.replica = aws } # no replication: pass the default twice (section 8)
name = "assets"
environment = "prod"
kms_key_arn = module.assets_key.key_arn
log_bucket_id = data.terraform_remote_state.logging.outputs.access_log_bucket_id
tags = local.service_tags # mandatory keys from section 7, except environment
}
Replicated call (examples/replicated):
module "exports_bucket" {
source = "registry.corvid.example/platform/object-bucket/aws"
version = "~> 2.3"
providers = { aws = aws, aws.replica = aws.eu_central_1 }
name = "exports"
environment = "prod"
kms_key_arn = module.exports_key.key_arn
log_bucket_id = data.terraform_remote_state.logging.outputs.access_log_bucket_id
tags = local.service_tags
enable_replication = true
replication = {
destination_bucket_arn = module.exports_replica_bucket.bucket_arn
kms_key_arn = module.exports_replica_key.key_arn
}
}
Both examples are planned in CI on every pull request, and TST-06 applies the replicated one in the sandbox.
13. Release, support and deprecation
13.1 Release process
- A maintainer opens a release pull request that updates
CHANGELOG.mdand this specification. - CI runs every layer in 11.1; a second maintainer approves.
- The merge creates a signed tag
vX.Y.Z, and the registry publishes the version. - The release note is posted in [channel] and, for a major release, consuming teams named in 1.3 are notified directly.
13.2 Support policy (Example values)
| Item | Commitment |
|---|---|
| Supported major versions | Current major and the previous major |
| Security fixes | Back-ported to the previous major for 6 months after a new major |
| Response to issues | Triage within 2 working days |
| Usage tracking | Registry download data and a scheduled search for module sources in all infrastructure repositories |
13.3 Deprecation
- Announce deprecation of a version line or of the whole module with a reason, a replacement and a removal date at least [90] days ahead.
- Mark inputs as deprecated in their description and emit a warning through a
checkblock where useful, before removing them in the next major release. - Track remaining consumers weekly from usage tracking; contact owners directly in the last 30 days.
- After the removal date, the version stays in the registry for reproducibility but receives no fixes; the module status becomes Retired.
14. Glossary and references
14.1 Glossary
| Term | Meaning in this specification |
|---|---|
| Module | A set of configuration files in one directory, called by other configurations with inputs and outputs |
| Root module | The configuration where commands are run; it calls child modules |
| Guardrail | A setting the module enforces and callers cannot override |
| Policy as code | Rules written in a policy language and evaluated automatically against planned changes |
| Plan JSON | Machine-readable form of a saved plan, used by policy tools |
moved block | Declaration that a resource address changed, so state is updated instead of replaced |
| Pessimistic constraint | Version constraint ~> that allows only the rightmost component to increase |
| Drift | Difference between the declared configuration and the real infrastructure |
14.2 References
| Reference | Version or date checked | Use |
|---|---|---|
| Semantic Versioning 2.0.0, https://semver.org/ | 2.0.0, checked 2026-10-24 | Release type rules (section 10) |
OpenTofu documentation, standard module structure and tofu test, https://opentofu.org/docs/ | OpenTofu 1.13.2 (released 2026-10-21) | Repository layout, test files and blocks |
| Terraform documentation, tests, https://developer.hashicorp.com/terraform/language/tests | Terraform 1.17.0 (released 2026-10-21) | Test command versions (tests 1.6, mocks 1.7) |
| Open Policy Agent, https://www.openpolicyagent.org/ | OPA 1.21.4; Conftest 0.71.1 | Policy as code on plan JSON |
| Keep a Changelog, https://keepachangelog.com/ | 1.1.0 | Changelog format |
| Conventional Commits, https://www.conventionalcommits.org/ | 1.0.0 | Optional commit convention |
Cost Allocation and Tagging Standard (ops/cost-allocation-tagging-standard) | [Version] | Tag keys and values |
Standard and licence note
Based on Semantic Versioning 2.0.0 (semver.org, CC BY 3.0) for release rules, and structure aligned with the OpenTofu standard module structure and the native test command documented for OpenTofu and Terraform; the policy example uses Open Policy Agent (Apache-2.0). No vendor documentation text is reproduced; code samples are original examples. OpenTofu is a trademark of LF Projects, LLC; Terraform and HashiCorp, AWS and Amazon S3, and Open Policy Agent are trademarks of their owners; this template is not affiliated with or endorsed by them. This template is original text written for this collection. OpenTofu is licensed under MPL-2.0; Terraform 1.6 and later is under the Business Source License 1.1. Check the current official documentation and tool versions before use. Template licence: MIT.
Template liên quan
Access Review Template
Periodic access audit – keep/modify/revoke
Vận hànhAlerting Policy Template (SLO Burn-Rate Alerts)
Symptom-based paging, page vs ticket vs log, multi-window burn-rate rules with worked numbers, alert metadata, naming, routing, noise budget, review, alerts as code
Vận hànhBackup Policy Template (3-2-1-1-0, Immutability, NIST CP-9)
Organisation backup policy: data classes and backup tiers, numbered MUST/SHOULD statements, 3-2-1-1-0, immutable copies, retention, key escrow, ransomware isolation, restore tests, metrics