ADR-022: Release Management Automation
Status
Accepted
Date
2026-02-28
Deciders
@product-owner, @cloud-architect, HITL/Manager
Context
Problem Statement
The terraform-aws monorepo had three active release-management defects as of 2026-02-28:
BUG-REG-002: Tag collision on manual push
The HITL manually ran git tag v1.1.0 && git push origin v1.1.0 after a VERSION bump
commit. The tag already existed from a prior push. git push failed with
rejected: tag already exists. The downstream registry-publish.yml never fired.
The module was not published.
BUG-REL-001: VERSION drift between root and module
VERSION at repo root and modules/iam-identity-center/VERSION could diverge if
only one file was edited. No CI gate enforced consistency.
SIC-001: TFC Private Registry subdirectory misconfiguration
TFC was connected to the repository root (/) instead of modules/iam-identity-center.
Terraform Cloud's registry UI has no "edit subdirectory" option for existing modules.
Fix requires delete and re-add with the correct subdirectory.
Current State at ADR Date
| Deliverable | Status |
|---|---|
.github/workflows/release-please.yml | EXISTS — triggers on push to main |
release-please-config.json | EXISTS — type: simple, extra-files: VERSION + modules/iam-identity-center/VERSION |
.release-please-manifest.json | EXISTS — {"." : "1.1.0"} |
.commitlintrc.json | EXISTS — conventional commit types enforced |
.github/workflows/registry-publish.yml | EXISTS — module-prefixed tag resolution (MODULE_NAME/v*) |
Taskfile.yml release:tag-check | EXISTS — git ls-remote existence check |
Taskfile.yml registry:preflight | EXISTS — composite pre-flight gate |
.claude/skills/governance/conventional-commits.md | EXISTS — v1.0.0 with type table and scope convention |
docs/QUICKSTART-REGISTRY-PUBLISH.md | EXISTS — HITL copy/paste guide for SIC-001 + v1.2.0 publish |
| ADR-022 (this document) | CREATED 2026-02-28 |
Gap identified: release-please-config.json uses release-type: simple rather than
terraform-module. The simple type does not produce a dedicated CHANGELOG per module;
it treats the root as a single package. This is acceptable for Phase 1 (monorepo, single
module in active use) but must be revisited for Phase 2 when ecs-platform is published.
Decision
Part A: release-please Workflow (Phase 1)
Adopt google-github-actions/release-please-action@v4 as the single mechanism for:
- Creating Release PRs with computed VERSION bump and CHANGELOG entries.
- Creating git tags on HITL merge.
- Triggering
registry-publish.ymlvia the tag push event.
HITL gate preserved: release-please opens a PR but does NOT merge it autonomously. HITL reviews and merges the Release PR. This satisfies ADLC Principle I (Acceptable Agency).
Prohibited after adoption:
- Manual
git tagandgit push origin <tag>for version releases. - Manual edits to
VERSIONfiles (release-please owns these). - Manual edits to
CHANGELOG.md(release-please owns the version entries; HITL may add notes above the automated section).
Part B: Module-Prefixed Tag Format
registry-publish.yml supports two tag formats:
| Format | Example | Use |
|---|---|---|
| Module-prefixed (canonical) | iam-identity-center/v1.2.0 | Production — triggers per-module publish |
| Global legacy (deprecated) | v1.1.0 | Backward compat only — defaults to iam-identity-center module; remove in Phase 4 |
For Phase 1, release-please creates global tags (v1.2.0) because release-please-config.json
uses the root package pattern. The registry-publish.yml legacy fallback handles this.
For Phase 2 (multi-module release), the config will be migrated to per-package configuration:
{
"packages": {
"modules/iam-identity-center": {
"release-type": "terraform-module",
"include-component-in-tag": true,
"tag-separator": "/"
},
"modules/ecs-platform": {
"release-type": "terraform-module",
"include-component-in-tag": true,
"tag-separator": "/"
}
}
}
This produces iam-identity-center/v1.2.0 natively without the legacy fallback.
Part C: Conventional Commits Enforcement
All commits to main follow Conventional Commits v1.0.0. Enforcement via commitlint
in CI (.commitlintrc.json present). Advisory mode for 14 days post-adoption;
blocking mode thereafter.
Semver derivation:
feat:→ MINOR bumpfix:/perf:→ PATCH bumpfeat!:/BREAKING CHANGE:footer → MAJOR bumpchore:/ci:/docs:/test:→ no release PR created
Part D: Taskfile Pre-Flight Gates
task registry:preflight MODULE=<name> is the mandatory pre-flight command before
any release action. It runs in sequence:
release:tag-check— git ls-remote confirms tag does not already existci:quick— fmt + validate + lint + legaltest:tier1— snapshot tests passgovern:legal— Apache 2.0 headers present- VERSION consistency check — root
VERSION==modules/<name>/VERSION - CHANGELOG advisory check — entry for current version exists
Consequences
Positive
- BUG-REG-002 (tag collision) eliminated: release-please is idempotent; tag creation happens via PR merge, not manual CLI command.
- BUG-REL-001 (VERSION drift) eliminated: release-please updates both VERSION files
atomically via
extra-filesconfig. - HITL toil reduced: manual release process (~30 min) → PR review + merge (~5 min).
- Audit trail: every release is a merged PR with reviewer, timestamp, and CHANGELOG diff.
- ADLC Principle I preserved: HITL must merge Release PR; autonomous agent cannot release.
Negative
chore:/ci:/docs:commits do not trigger a Release PR. Teams accustomed to frequent patch releases must usefix:type for any user-visible fix.release-type: simpledoes not generate per-module CHANGELOG entries. Acceptable for Phase 1 (single active module); requires migration toterraform-moduletype for Phase 2.- If branch protection prevents GITHUB_TOKEN from creating PRs, a PAT must be created
and stored as
RELEASE_PLEASE_PATActions secret. HITL must provision this.
Risks
| ID | Risk | Probability | Impact | Mitigation |
|---|---|---|---|---|
| RISK-022-001 | GITHUB_TOKEN blocked by branch protection | MEDIUM | MEDIUM | Create RELEASE_PLEASE_PAT; store as Actions secret |
| RISK-022-002 | Non-conventional commits on main suppress Release PR | MEDIUM | LOW | commitlint enforcement; HITL can manually trigger release-please |
| RISK-022-003 | Phase 2 per-package migration breaks existing Release PR in flight | LOW | MEDIUM | Complete any open Release PR before migrating config |
| RISK-022-004 | Legacy global tag (v1.2.0) fires for all modules in Phase 2 | MEDIUM | HIGH | Migrate config to per-package before adding second module |
Alternatives Considered
Alternative 1: Manual VERSION + git tag (rejected)
Current state prior to this ADR. Root cause of BUG-REG-002. Manual process cannot enforce consistency or prevent human error.
Alternative 2: Semantic Release (rejected)
Semantic Release (semantic-release/semantic-release) is more configurable but
requires Node.js in CI and has a steeper learning curve. release-please has
native terraform-module support and is maintained by Google. Switching cost
exceeds benefit.
Alternative 3: GitHub Releases UI (rejected)
Creating releases through the GitHub web UI bypasses VERSION file management. No automation path to TFC. Rejected.
References
- release-please: https://github.com/googleapis/release-please
- release-please-action v4: https://github.com/google-github-actions/release-please-action
- Conventional Commits v1.0.0: https://www.conventionalcommits.org/en/v1.0.0/
- US-RP-001 through US-RP-006:
DevOps-TechDocs/docs/docs/terraform-aws/invest-stories/US-release-please-phase1-phase2.mdx - Quickstart (HITL):
/Volumes/Working/projects/terraform-aws/docs/QUICKSTART-REGISTRY-PUBLISH.md - Conventional Commits skill:
.claude/skills/governance/conventional-commits.md - ADR-002 (Registry Structure): defines Phase 1 monorepo and Phase 2 thin-wrapper pattern
- Coordination evidence:
tmp/terraform-aws/coordination-logs/product-owner-2026-02-28-rq1-rq3.json