ADR-023: Derived Module Pattern (Clone + Value-Add)
- Status: Accepted
- Date: 2026-03-08
- Deciders: HITL/Manager, Product Owner, Cloud Architect
- Supersedes: CLAUDE.md Architecture note ("Wrapper Pattern" — corrected to "Derived Module Pattern")
Context
As the terraform-aws monorepo grew from 2 modules (sso, ecs) to 8 modules (sso, ecs, web, alb, acm, cloudfront, s3, vpc), a consistent pattern for sourcing upstream infrastructure code became necessary. Three strategies were considered:
| Strategy | Description | Risk |
|---|---|---|
| Thin wrapper | module { source = "terraform-aws-modules/vpc/aws" } — consume upstream directly at call time | Runtime dependency on public registry; upstream breaking changes affect production immediately; no ability to enforce oceansoft security defaults at source |
| Derived (clone + value-add) | Clone upstream source into modules/<name>/, apply oceansoft value-adds, publish independently | Manual upstream tracking burden; fully independent publication; security defaults enforced at source |
| Custom from scratch | Write all resources directly without upstream baseline | Full control; highest maintenance burden; reinvents well-tested upstream code |
All active modules (alb, acm, cloudfront, s3, vpc) were cloned from
terraform-aws-modules/<name> under Apache-2.0. The sso module was
cloned from aws-ia/terraform-aws-sso (ADR-007). The ecs module was
cloned from terraform-aws-modules/ecs. The web module is an exception — it is a
composition layer (ADR-024) with no direct upstream equivalent.
The thin-wrapper pattern was initially documented in CLAUDE.md but was not the implementation reality. This ADR formalizes the pattern as "derived module" to match the actual codebase.
Decision
All modules in this monorepo use the derived module pattern:
- Clone the upstream
terraform-aws-modules/<name>source intomodules/<name>/ - Apply oceansoft value-adds (enumerated below)
- Maintain independent semver per module (ADR-025)
- Publish to
app.terraform.io/app/oceansoft/TFC private registry
Oceansoft Value-Adds Applied to Every Derived Module
| Value-Add | Location | Purpose |
|---|---|---|
| Copyright header | All .tf files | Apache-2.0 attribution: [email protected] (oceansoft.io) |
NOTICE.txt | Module root | Credits upstream source per Apache-2.0 Section 4(d) |
| Provider constraints | versions.tf | aws >= 6.28, < 7.0; terraform >= 1.11.0 (ADR-003) |
provider_meta "aws" | versions.tf | user_agent for TFC registry tracking |
| Security defaults | Module variables/resources | TLS 1.3 minimum, HTTPS-only listeners, deletion protection enabled |
| APRA CPS 234 compliance | Variables, examples | data_classification tag, audit trail, Compliance = "APRA-CPS234" |
| FOCUS 1.2+ tags | Example versions.tf / default_tags | x_cost_center, x_environment, x_project, x_service_name |
| Tier 1 snapshot tests | tests/*.tftest.hcl | Free 2-3s validation, no AWS credentials (ADR-004) |
example naming | examples/ subdirs | mvp-, poc-, production- prefix (ADR-005) |
What Is NOT Changed
- Core resource logic from upstream (subnet calculations, route table associations, etc.)
provider_meta "aws" { user_agent }— updated to reflect oceansoft lineage, not deleted- Upstream variable names and types — preserved for operator familiarity
Consequences
Positive
- No runtime dependency on public Terraform Registry — production deployments are air-gap safe
- Security defaults (TLS 1.3, deletion protection) are enforced at source, not at call site
- APRA CPS 234 and FOCUS 1.2+ compliance is built into the module, not left to consumers
- Each module is independently publishable with a credible output surface (full ARNs, IDs)
- Upstream module APIs are well-tested by the community before we adopt them
Negative
- Upstream security patches must be tracked manually — quarterly diff review against upstream tags
- Merge conflict surface when upstream makes structural changes (variable renames, resource restructures)
- Each cloned module inherits upstream's line count (vpc: 4,059 lines) — not suitable for rapid internal modules
Risks
| ID | Risk | Probability | Impact | Mitigation |
|---|---|---|---|---|
| RISK-023-001 | Upstream CVE fix not backported within SLA | MEDIUM | HIGH | Quarterly upstream diff review; task security:trivy in CI gates each release |
| RISK-023-002 | Provider version conflict between cloned modules | LOW | MEDIUM | All modules share ADR-003 constraint (>= 6.28, < 7.0); single lockfile per project |
| RISK-023-003 | Upstream module deprecation (e.g., terraform-aws-modules/vpc v6) | LOW | MEDIUM | Pinned provider_meta user_agent enables usage tracking; ADR review on major upstream bump |
Upstream Tracking Schedule
| Frequency | Action |
|---|---|
| Quarterly | git diff upstream/<module>/vX.Y.Z against the version we cloned from |
| On CVE | Immediate patch backport via fix: commit → release-please PATCH bump |
| On major upstream release | Cloud Architect assessment → new ADR if structural change required |
Alternatives Considered
-
Thin wrapper (
module { source = "..." }): Rejected — runtime public registry dependency; cannot enforce security defaults at source; upstream breaking change propagates immediately to all consumers without a review gate. -
Custom from scratch: Rejected for foundational modules (vpc, alb, acm, etc.) — reinvents 3,000-5,000 lines of community-tested code. Acceptable for composition layers with no upstream equivalent (see ADR-024 for the web module).
-
Git submodule pointing to upstream: Rejected — submodule checkout pinned to upstream commit does not permit value-add modifications; publishing a submodule reference to TFC is unsupported.
Applicability
| Module | Upstream Source | Derived? |
|---|---|---|
sso | aws-ia/terraform-aws-sso | Yes (ADR-007) |
ecs | terraform-aws-modules/ecs | Yes |
alb | terraform-aws-modules/alb | Yes |
acm | terraform-aws-modules/acm | Yes |
cloudfront | terraform-aws-modules/cloudfront | Yes |
s3 | terraform-aws-modules/s3 | Yes |
vpc | terraform-aws-modules/vpc | Yes |
web | None (composition layer) | No — see ADR-024 |
Related ADRs
- ADR-001: Module naming (kebab-case)
- ADR-002: Registry structure
- ADR-003: Provider constraints
- ADR-007: IAM Identity Center upstream strategy (first instance of this pattern)
- ADR-024: Composition layer (web module exception)
- ADR-025: Per-component semver
Coordination Evidence
- Product Owner log:
tmp/terraform-aws/coordination-logs/product-owner-2026-03-08.json - Cloud Architect log:
tmp/terraform-aws/coordination-logs/cloud-architect-2026-03-08.json - Architecture decisions:
tmp/terraform-aws/architecture-decisions/ADR-023-derived-module-pattern-2026-03-08.md