tofu-module-publish: .post default means tag pipelines are never created, so modules silently never publish

Summary

tofu-module-publish defaults to stage: .post. Combined with the gate components' tag exclusions, that means a consuming repo's tag pipeline is never created, so the module silently never publishes.

The default guarantees the failure for any consumer that does not override it — and the component's entire purpose is publishing on tag, which is exactly when it bites.

Why it fails

  1. tofu-module-publish defaults to stage: .post.
  2. tofu-lint / tofu-security / tofu-validate all carry if: $CI_COMMIT_TAGwhen: never, so every other job self-excludes on tags.
  3. On a tag, the publish job is therefore the pipeline's only job.
  4. GitLab does not create a pipeline whose jobs are all in .pre/.post.

Result: no tag pipeline at all. Triggering one by hand shows "Pipeline cannot be run. The resulting pipeline would have been empty."

Why it went unnoticed for two months

The failure is completely silent:

  • the release MR merges green
  • the tag is created
  • the GitLab Release page appears
  • the registry receives nothing
  • no pipeline exists, so nothing goes red

Every signal a maintainer would normally check says the release succeeded.

Observed impact

Three of the four iac/* module repos have their latest tag missing from the registry:

Repo stage Latest tag In registry
terraform-aws-gitlab-runner-fleet release (overridden) v0.1.2 v0.1.2
terraform-aws-signing-kms .post (default) v0.2.0 0.1.0–0.1.2
terraform-aws-bootstrap .post (default) v0.3.1 0.2.1, 0.2.2, 0.3.0
terraform-aws-security-baseline .post (default) v0.2.1 0.2.0

The one repo that overrides the default is the one that works — the estate contains its own control.

This surfaced when infra tried to consume signing-kms 0.2.0 and tofu-validate reported "no available version … the newest available version is 0.1.2", two months after the breakage began.

Regression window

The component was fine when consumers were on cicd@v0.6.0, whose gate jobs used an unconditional rules: [- when: on_success] and therefore ran on tag pipelines — giving the tag pipeline a job in a normal stage. The tag exclusions added later removed that job without anything noticing the publish job had been left stranded in .post.

So neither change was wrong on its own; the interaction is.

Suggested fix

Change the stage input's default away from .postrelease matches what consumers already declare, and is what the one working repo passes explicitly.

Worth considering alongside it:

  • A guard so this cannot recur silently. Any component that is the sole job on some pipeline type is exposed to the same trap. A note in the component docs is the cheap version; a lint/test asserting a tag pipeline is non-empty is the real one.
  • Publish verification. Even with the stage fixed, nothing asserts the module is resolvable afterwards. A post-publish check would turn a future silent failure into a red job.

Workaround for consumers

Pass stage: release explicitly. Applied to the three affected repos in:

Note this does not retroactively publish already-created tags — their .gitlab-ci.yml is frozen at the tagged commit, so each repo needs a fresh release afterwards.