Skip to main content

ADR-025: Per-Component Versioning

  • Status: Accepted
  • Date: 2026-03-08
  • Deciders: HITL/Manager, Product Owner, Cloud Architect
  • Builds on: ADR-022 (Release Management Automation)

Context

The terraform-aws monorepo contains 8 modules as of 2026-03-08:

ModuleCurrent Version
sso1.3.0
ecs1.1.0
web1.0.2
acm1.0.0
alb1.0.0
cloudfront1.0.0
s31.0.0
vpc(new — not yet versioned)

Two versioning strategies were considered:

Single repo version: One VERSION file at the root. All modules release together. Every commit touching any module bumps the version for all modules. A fix: in ecs forces a patch release of sso even if nothing changed.

Per-component version: Each module has its own VERSION file and CHANGELOG. A feat(ecs): commit bumps only ecs. The TFC registry receives a ecs/v1.2.0 tag and publishes only that module. Other modules are unaffected.

ADR-022 adopted release-please as the release automation tool. The release-please-config.json was subsequently updated (Phase 2 migration, completed before this ADR date) to use per-package configuration with include-component-in-tag: true and tag-separator: "/". This ADR formalizes that Phase 2 state as an accepted architectural decision.


Decision

Each module in the monorepo has an independent semantic version governed by:

  1. release-please per-package config (release-please-config.json) — one entry per modules/<name> path
  2. Conventional Commits with module scopefeat(vpc):, fix(alb):, perf(sso):
  3. Module-prefixed git tags — format <module-name>/vX.Y.Z (e.g., vpc/v1.0.0)
  4. Per-module VERSION filemodules/<name>/VERSION updated atomically by release-please
  5. Per-module CHANGELOG.mdmodules/<name>/CHANGELOG.md

Tag Format

<module-name>/vX.Y.Z

Examples:
sso/v1.3.0
ecs/v1.1.0
vpc/v1.0.0

Tags are created by release-please on HITL merge of the Release PR. The registry-publish.yml workflow filters on **/v*.*.* tag patterns and resolves the module name from the tag prefix.

release-please Config (canonical form)

{
"tag-separator": "/",
"include-component-in-tag": true,
"packages": {
"modules/vpc": {
"release-type": "simple",
"component": "vpc",
"changelog-path": "CHANGELOG.md",
"extra-files": ["VERSION"],
"bump-minor-pre-major": true
}
}
}

Each module entry follows this schema. bump-minor-pre-major: true ensures feat: commits produce 0.x0.(x+1).0 bumps when the module is pre-1.0.

Semver Derivation Rules (per module)

Commit Type + ScopeVersion BumpExample
feat(<module>):MINORfeat(vpc): add VPN gateway support
fix(<module>): / perf(<module>):PATCHfix(ecs): correct task exec role ARN
feat(<module>)!: or BREAKING CHANGE: footerMAJORfeat(vpc)!: rename cidr variable
chore: / ci: / docs: (unscoped or other scope)No releasedocs: update README

Commits with no scope or a scope that does not match a module path do NOT trigger a release PR for any module.

HITL Gate Preserved

release-please opens a Release PR but does NOT merge it. The HITL reviews the proposed version bump and CHANGELOG, then merges. Autonomous agents MUST NOT merge Release PRs. This preserves ADLC Principle I (Acceptable Agency).


Consequences

Positive

  1. A fix in ecs does not force an unnecessary version bump for sso consumers
  2. TFC registry receives correctly-scoped module tags; operators pin to vpc/v1.0.0 independently
  3. CHANGELOG per module provides a clean audit trail of changes to each component
  4. feat(vpc): commits produce the right semver signal to consumers of that module
  5. The .release-please-manifest.json is the single source of truth for all current versions

Negative

  1. 8 modules = up to 8 open Release PRs simultaneously during active development sprints
  2. Conventional commit scope discipline is required from all contributors — a mis-scoped commit (feat: without scope) goes to the root package and may not reach module consumers
  3. Adding a new module requires updating release-please-config.json, .release-please-manifest.json, and creating the initial VERSION file — a 3-file bootstrap step

Risks

IDRiskProbabilityImpactMitigation
RISK-025-001Contributor omits module scope — feat: instead of feat(vpc):HIGHLOWcommitlint scope enforcement + HITL PR review checks scope
RISK-025-002HITL merges multiple Release PRs out of order causing manifest conflictLOWMEDIUMrelease-please is idempotent; re-run resolves conflicts automatically
RISK-025-003VERSION drift between manifest and modules/<name>/VERSIONLOWHIGHPre-publish 4-file consistency check (version-sync-validation.md skill)
RISK-025-004New module added without manifest entry — silent versioning gapMEDIUMMEDIUMBootstrap checklist in deploy-lifecycle.md; task sprint:validate checks manifest completeness

Module Bootstrap Checklist (for new modules)

When adding a new module to the monorepo:

  1. Add entry to release-please-config.json under "packages" with correct component name
  2. Add entry to .release-please-manifest.json with initial version (e.g., "modules/vpc": "0.1.0")
  3. Create modules/<name>/VERSION containing 0.1.0
  4. Create modules/<name>/CHANGELOG.md with stub header
  5. Verify task sprint:validate passes (includes manifest completeness check)

Current Version State (2026-03-08)

From .release-please-manifest.json:

{
"modules/sso": "1.3.0",
"modules/ecs": "1.1.0",
"modules/web": "1.0.2",
"modules/acm": "1.0.0",
"modules/alb": "1.0.0",
"modules/cloudfront": "1.0.0",
"modules/s3": "1.0.0"
}

Note: modules/vpc is not yet in the manifest. It must be bootstrapped per the checklist above before the first vpc release PR can be created.


Alternatives Considered

  1. Single monorepo version (root VERSION file only): Rejected — forces all module consumers to upgrade to a new version even when no relevant changes occurred; violates independent deployability of each TFC registry module.

  2. Git tags without release-please automation: Rejected — manual tag process caused BUG-REG-002 (tag collision) documented in ADR-022. release-please provides idempotency and CHANGELOG automation.

  3. CalVer (e.g., 2026.03.0): Rejected — TFC private registry expects semver; CalVer does not express breaking changes vs features vs patches.


  • ADR-022: Release management automation (release-please adoption — Phase 1)
  • ADR-002: Registry structure (module-prefixed tags consumed by registry-publish.yml)
  • ADR-023: Derived module pattern (each derived module gets its own version)

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-025-per-component-versioning-2026-03-08.md