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 scope — feat(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 file — modules/<name>/VERSION updated atomically by release-please
  5. Per-module CHANGELOG.md — modules/<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.x → 0.(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