fix(ci): derive OCI publish mode from trigger to end twin-publish race #61

Merged
David merged 1 commit from fix/GOV-15-twin-trigger-publish-mode into main 2026-06-26 18:10:51 +02:00
Owner

Ports the GOV-13 twin-trigger publish-mode fix into rusty-links' single OCI build workflow, as part of the GOV-15 per-repo rollout.

The build workflow triggered on push to both branches: [main] and tags: [v*] and resolved its tag set from git describe via oci-build/get-tags.nu --joined, which returned [vX.Y.Z, latest] on a tagged commit. Because a release commit is simultaneously a main push and a v* tag push, both events fired, both built the same image, and both raced to overwrite the mutable :latest tag (and 409'd on immutable artifacts). The fix derives the publish MODE from the trigger rather than from git describe, so the two runs push DISJOINT tags and never write the same destination.

oci-build/get-tags.nu is replaced with the canonical --mode version that returns a { mode, train, tag, describe } record: a v* tag ref resolves the release train (publishes only the immutable vX.Y.Z), a main push resolves the latest train (publishes only the rolling latest), and a workflow_dispatch resolves a dry-run that builds and prints without mutating the registry.

The workflow gains a workflow_dispatch trigger with dry_run/simulate_tag inputs, a "Resolve publish mode, train and tag" step that derives the mode from github.event_name/github.ref_type, and single-tag build/verify/print steps gated on dry-run. Everything repo-specific is preserved unchanged: the image name, registry/secret wiring, the type=gha build cache, the --iidfile digest capture, the registry digest-verification step, and the Dockerfile path. On a dry-run the build uses --load and writes no cache, and the verify step is skipped because nothing was pushed.

Verification run locally: YAML parses, nu-check on the helper returns true, all four functional modes resolve the expected tag/train (release v9.9.9, latest, dry-run simulate v9.9.9 -> release, dry-run plain -> latest), and both error paths (unknown mode, release with no version) exit non-zero. The pre-commit hook (fmt + full build + tests) passed.

Ports the GOV-13 twin-trigger publish-mode fix into rusty-links' single OCI build workflow, as part of the GOV-15 per-repo rollout. The build workflow triggered on push to both `branches: [main]` and `tags: [v*]` and resolved its tag set from `git describe` via `oci-build/get-tags.nu --joined`, which returned `[vX.Y.Z, latest]` on a tagged commit. Because a release commit is simultaneously a main push and a `v*` tag push, both events fired, both built the same image, and both raced to overwrite the mutable `:latest` tag (and 409'd on immutable artifacts). The fix derives the publish MODE from the trigger rather than from `git describe`, so the two runs push DISJOINT tags and never write the same destination. `oci-build/get-tags.nu` is replaced with the canonical `--mode` version that returns a `{ mode, train, tag, describe }` record: a `v*` tag ref resolves the `release` train (publishes only the immutable `vX.Y.Z`), a main push resolves the `latest` train (publishes only the rolling `latest`), and a `workflow_dispatch` resolves a dry-run that builds and prints without mutating the registry. The workflow gains a `workflow_dispatch` trigger with `dry_run`/`simulate_tag` inputs, a "Resolve publish mode, train and tag" step that derives the mode from `github.event_name`/`github.ref_type`, and single-tag build/verify/print steps gated on dry-run. Everything repo-specific is preserved unchanged: the image name, registry/secret wiring, the `type=gha` build cache, the `--iidfile` digest capture, the registry digest-verification step, and the Dockerfile path. On a dry-run the build uses `--load` and writes no cache, and the verify step is skipped because nothing was pushed. Verification run locally: YAML parses, `nu-check` on the helper returns true, all four functional modes resolve the expected tag/train (release v9.9.9, latest, dry-run simulate v9.9.9 -> release, dry-run plain -> latest), and both error paths (unknown mode, release with no version) exit non-zero. The pre-commit hook (fmt + full build + tests) passed.
fix(ci): derive OCI publish mode from trigger to end twin-publish race
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 57s
Create release / Create release from merged PR (pull_request) Has been skipped
6185787685
The build workflow triggered on push to both `branches: [main]` and `tags: [v*]` and resolved its tag set from `git describe` via `oci-build/get-tags.nu --joined`, which returned `[vX.Y.Z, latest]` on a tagged commit. A release commit is simultaneously a main push and a `v*` tag push, so both events fired, both built the same image, and both raced to overwrite the mutable `:latest` tag (and 409'd on immutable artifacts).

Derive the publish MODE from the trigger instead of `git describe`: a `v*` tag ref resolves the `release` train and publishes only the immutable `vX.Y.Z`, a main push resolves the `latest` train and publishes only the rolling `latest`, and a `workflow_dispatch` resolves a dry-run that builds and prints without mutating the registry. The two real triggers now write DISJOINT tags, so the two runs never race for the same destination.

`get-tags.nu` is replaced with the canonical `--mode` version returning a `{ mode, train, tag, describe }` record; the workflow gains a `workflow_dispatch` trigger with `dry_run`/`simulate_tag` inputs, a "Resolve publish mode, train and tag" step, and single-tag build/verify/print steps gated on dry-run. The repo's gha build cache, `--iidfile` digest capture, and registry digest verification are preserved unchanged.

This ports the GOV-13 fix as part of the GOV-15 per-repo rollout.

#GOV-15
#GOV-13
David merged commit fcdfbd1a18 into main 2026-06-26 18:10:51 +02:00
David deleted branch fix/GOV-15-twin-trigger-publish-mode 2026-06-26 18:10:51 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
a8n-tools/rusty-links!61
No description provided.