Skip to main content

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:

StrategyDescriptionRisk
Thin wrappermodule { source = "terraform-aws-modules/vpc/aws" } — consume upstream directly at call timeRuntime 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 independentlyManual upstream tracking burden; fully independent publication; security defaults enforced at source
Custom from scratchWrite all resources directly without upstream baselineFull 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:

  1. Clone the upstream terraform-aws-modules/<name> source into modules/<name>/
  2. Apply oceansoft value-adds (enumerated below)
  3. Maintain independent semver per module (ADR-025)
  4. Publish to app.terraform.io/app/oceansoft/ TFC private registry

Oceansoft Value-Adds Applied to Every Derived Module

Value-AddLocationPurpose
Copyright headerAll .tf filesApache-2.0 attribution: [email protected] (oceansoft.io)
NOTICE.txtModule rootCredits upstream source per Apache-2.0 Section 4(d)
Provider constraintsversions.tfaws >= 6.28, < 7.0; terraform >= 1.11.0 (ADR-003)
provider_meta "aws"versions.tfuser_agent for TFC registry tracking
Security defaultsModule variables/resourcesTLS 1.3 minimum, HTTPS-only listeners, deletion protection enabled
APRA CPS 234 complianceVariables, examplesdata_classification tag, audit trail, Compliance = "APRA-CPS234"
FOCUS 1.2+ tagsExample versions.tf / default_tagsx_cost_center, x_environment, x_project, x_service_name
Tier 1 snapshot teststests/*.tftest.hclFree 2-3s validation, no AWS credentials (ADR-004)
example namingexamples/ subdirsmvp-, 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

  1. No runtime dependency on public Terraform Registry — production deployments are air-gap safe
  2. Security defaults (TLS 1.3, deletion protection) are enforced at source, not at call site
  3. APRA CPS 234 and FOCUS 1.2+ compliance is built into the module, not left to consumers
  4. Each module is independently publishable with a credible output surface (full ARNs, IDs)
  5. Upstream module APIs are well-tested by the community before we adopt them

Negative

  1. Upstream security patches must be tracked manually — quarterly diff review against upstream tags
  2. Merge conflict surface when upstream makes structural changes (variable renames, resource restructures)
  3. Each cloned module inherits upstream's line count (vpc: 4,059 lines) — not suitable for rapid internal modules

Risks

IDRiskProbabilityImpactMitigation
RISK-023-001Upstream CVE fix not backported within SLAMEDIUMHIGHQuarterly upstream diff review; task security:trivy in CI gates each release
RISK-023-002Provider version conflict between cloned modulesLOWMEDIUMAll modules share ADR-003 constraint (>= 6.28, < 7.0); single lockfile per project
RISK-023-003Upstream module deprecation (e.g., terraform-aws-modules/vpc v6)LOWMEDIUMPinned provider_meta user_agent enables usage tracking; ADR review on major upstream bump

Upstream Tracking Schedule

FrequencyAction
Quarterlygit diff upstream/<module>/vX.Y.Z against the version we cloned from
On CVEImmediate patch backport via fix: commit → release-please PATCH bump
On major upstream releaseCloud Architect assessment → new ADR if structural change required

Alternatives Considered

  1. 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.

  2. 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).

  3. 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

ModuleUpstream SourceDerived?
ssoaws-ia/terraform-aws-ssoYes (ADR-007)
ecsterraform-aws-modules/ecsYes
albterraform-aws-modules/albYes
acmterraform-aws-modules/acmYes
cloudfrontterraform-aws-modules/cloudfrontYes
s3terraform-aws-modules/s3Yes
vpcterraform-aws-modules/vpcYes
webNone (composition layer)No — see ADR-024

  • 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