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:
| Module | Current Version |
|---|---|
sso | 1.3.0 |
ecs | 1.1.0 |
web | 1.0.2 |
acm | 1.0.0 |
alb | 1.0.0 |
cloudfront | 1.0.0 |
s3 | 1.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:
- release-please per-package config (
release-please-config.json) — one entry permodules/<name>path - Conventional Commits with module scope —
feat(vpc):,fix(alb):,perf(sso): - Module-prefixed git tags — format
<module-name>/vX.Y.Z(e.g.,vpc/v1.0.0) - Per-module
VERSIONfile —modules/<name>/VERSIONupdated atomically by release-please - 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 + Scope | Version Bump | Example |
|---|---|---|
feat(<module>): | MINOR | feat(vpc): add VPN gateway support |
fix(<module>): / perf(<module>): | PATCH | fix(ecs): correct task exec role ARN |
feat(<module>)!: or BREAKING CHANGE: footer | MAJOR | feat(vpc)!: rename cidr variable |
chore: / ci: / docs: (unscoped or other scope) | No release | docs: 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
- A fix in
ecsdoes not force an unnecessary version bump forssoconsumers - TFC registry receives correctly-scoped module tags; operators pin to
vpc/v1.0.0independently - CHANGELOG per module provides a clean audit trail of changes to each component
feat(vpc):commits produce the right semver signal to consumers of that module- The
.release-please-manifest.jsonis the single source of truth for all current versions
Negative
- 8 modules = up to 8 open Release PRs simultaneously during active development sprints
- 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 - Adding a new module requires updating
release-please-config.json,.release-please-manifest.json, and creating the initialVERSIONfile — a 3-file bootstrap step
Risks
| ID | Risk | Probability | Impact | Mitigation |
|---|---|---|---|---|
| RISK-025-001 | Contributor omits module scope — feat: instead of feat(vpc): | HIGH | LOW | commitlint scope enforcement + HITL PR review checks scope |
| RISK-025-002 | HITL merges multiple Release PRs out of order causing manifest conflict | LOW | MEDIUM | release-please is idempotent; re-run resolves conflicts automatically |
| RISK-025-003 | VERSION drift between manifest and modules/<name>/VERSION | LOW | HIGH | Pre-publish 4-file consistency check (version-sync-validation.md skill) |
| RISK-025-004 | New module added without manifest entry — silent versioning gap | MEDIUM | MEDIUM | Bootstrap 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:
- Add entry to
release-please-config.jsonunder"packages"with correct component name - Add entry to
.release-please-manifest.jsonwith initial version (e.g.,"modules/vpc": "0.1.0") - Create
modules/<name>/VERSIONcontaining0.1.0 - Create
modules/<name>/CHANGELOG.mdwith stub header - Verify
task sprint:validatepasses (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
-
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.
-
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.
-
CalVer (e.g., 2026.03.0): Rejected — TFC private registry expects semver; CalVer does not express breaking changes vs features vs patches.
Related ADRs
- 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