Skip to main content

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

DeliverableStatus
.github/workflows/release-please.ymlEXISTS — triggers on push to main
release-please-config.jsonEXISTS — type: simple, extra-files: VERSION + modules/iam-identity-center/VERSION
.release-please-manifest.jsonEXISTS — {"." : "1.1.0"}
.commitlintrc.jsonEXISTS — conventional commit types enforced
.github/workflows/registry-publish.ymlEXISTS — module-prefixed tag resolution (MODULE_NAME/v*)
Taskfile.yml release:tag-checkEXISTS — git ls-remote existence check
Taskfile.yml registry:preflightEXISTS — composite pre-flight gate
.claude/skills/governance/conventional-commits.mdEXISTS — v1.0.0 with type table and scope convention
docs/QUICKSTART-REGISTRY-PUBLISH.mdEXISTS — 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:

  1. Creating Release PRs with computed VERSION bump and CHANGELOG entries.
  2. Creating git tags on HITL merge.
  3. Triggering registry-publish.yml via 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 tag and git push origin <tag> for version releases.
  • Manual edits to VERSION files (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:

FormatExampleUse
Module-prefixed (canonical)iam-identity-center/v1.2.0Production — triggers per-module publish
Global legacy (deprecated)v1.1.0Backward 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 bump
  • fix: / perf: → PATCH bump
  • feat!: / BREAKING CHANGE: footer → MAJOR bump
  • chore: / 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:

  1. release:tag-check — git ls-remote confirms tag does not already exist
  2. ci:quick — fmt + validate + lint + legal
  3. test:tier1 — snapshot tests pass
  4. govern:legal — Apache 2.0 headers present
  5. VERSION consistency check — root VERSION == modules/<name>/VERSION
  6. 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-files config.
  • 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 use fix: type for any user-visible fix.
  • release-type: simple does not generate per-module CHANGELOG entries. Acceptable for Phase 1 (single active module); requires migration to terraform-module type for Phase 2.
  • If branch protection prevents GITHUB_TOKEN from creating PRs, a PAT must be created and stored as RELEASE_PLEASE_PAT Actions secret. HITL must provision this.

Risks

IDRiskProbabilityImpactMitigation
RISK-022-001GITHUB_TOKEN blocked by branch protectionMEDIUMMEDIUMCreate RELEASE_PLEASE_PAT; store as Actions secret
RISK-022-002Non-conventional commits on main suppress Release PRMEDIUMLOWcommitlint enforcement; HITL can manually trigger release-please
RISK-022-003Phase 2 per-package migration breaks existing Release PR in flightLOWMEDIUMComplete any open Release PR before migrating config
RISK-022-004Legacy global tag (v1.2.0) fires for all modules in Phase 2MEDIUMHIGHMigrate 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