Engineering Standards¶
Standing rules for every contribution to this module. Deviations need a spec entry justifying why.
1. Tagging (non-negotiable)¶
Every taggable resource accepts and propagates var.tags. Two-layer
pattern:
- Provider-level
default_tags(set by the caller): cross-cutting tags likeProject,ManagedBy,Repository. - Module-level
var.tags: exposed by every module, threaded through every taggable resource viamerge(var.tags, { … })for any per-resource additions. Module-supplied tags win on key conflict.
When adding a new taggable resource: it MUST take tags = local.module_tags,
which merges var.tags with the optional Component tag from
var.component_tag. No exceptions.
Standard tag set we expect callers to define¶
| Tag | Source | Required? |
|---|---|---|
Project |
provider default_tags |
Yes |
Environment |
provider default_tags |
Yes |
Stack |
provider default_tags |
Yes |
ManagedBy (always opentofu) |
provider default_tags |
Yes |
Repository |
provider default_tags |
Yes |
Component |
var.component_tag, via local.module_tags |
Optional |
Owner |
provider default_tags (when CODEOWNERS isn't enough) |
Recommended |
CostCenter |
provider default_tags |
Recommended once billing is wired up |
2. Security defaults¶
- No OIDC trust at all. This module creates no CI-assumable role, and adding one means reopening the decision in the spec rather than adding a variable. Role trust names IAM principals only.
- Customer-managed KMS keys. Both keys are customer-managed. Automatic rotation is off because AWS offers it only for symmetric keys; see rotation, retention and deletion.
prevent_destroyon irreversible resources. Both KMS keys, driven byvar.prevent_destroy(defaulttrue).
3. Module input/output discipline¶
- Every
variablehastypeanddescription. Sensitive inputs aresensitive = true. - Every
outputhasdescription. Sensitive outputs aresensitive = true. - No provider configuration inside modules. The caller configures providers.
- No backend configuration inside modules. Modules don't manage their own state.
- Module sources for external dependencies are pinned (commit SHA or
semver tag).
terraform_module_pinned_sourceis enforced via tflint.
4. Naming¶
- Locals:
snake_case, singular. - AWS resource
nameattributes:kebab-case, prefixed with the project tag (pbs-<purpose>). Caller can override via inputs. - Variables:
snake_case, named for what they control (reader_require_mfa,allow_administrator_key_deletion). - Outputs:
snake_case, describe the shape (encryption_key_arn, notarn).
5. File organisation within a module¶
main.tf: resource definitions.variables.tf: typed, described inputs.outputs.tf: described outputs.versions.tf:required_version+required_providers.locals.tf: shared locals (optional).data.tf: data sources (optional).README.md: usage example + auto-generated inputs/outputs table from terraform-docs.
Larger modules may split main.tf by concern (main.policy.tf, etc.).
6. Versioning¶
- Pre-1.0: minor bumps may break the input/output surface. Describe the break
in a
Release-Note:commit trailer, never aBREAKING CHANGE:footer, which below 1.0 makes colophon cut v1.0.0. - Post-1.0: semver strictly.
- Tag releases as
v0.1.0,v0.1.1,v1.0.0, etc.
7. Commit style¶
Conventional Commits. This is a single module with no sub-modules, so scope
names the area touched (fix(policy):, docs(policy):) or is omitted. For
CI/workflows use ci.
No AI attribution in commit messages.