Skip to content

fix: separate edge and release image channels - #54

Merged
IceCodeNew merged 1 commit into
masterfrom
codex/fix-release-image-tags
Jul 17, 2026
Merged

fix: separate edge and release image channels#54
IceCodeNew merged 1 commit into
masterfrom
codex/fix-release-image-tags

Conversation

@IceCodeNew

@IceCodeNew IceCodeNew commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • publish master builds as edge and sha-<commit>
  • publish exact X.Y.Z tag builds as the matching version, latest, and sha-<commit>
  • remove the static image version, manual image publication, and merge-queue image builds
  • create all event-specific manifest tags with one docker buildx imagetools create command
  • cancel superseded master builds while isolating every release tag so edge activity or another release cannot cancel formal publication
  • atomically push the release commit and matching Git tag from a master-only release job

Root cause

Every master build reused the static 1.0.0 image tag, so production could run post-release source while still reporting version 1.0.0.

Trigger and concurrency boundaries

Renovate is already configured for branch automerge where eligible. An update builds an image only after it reaches master; unmerged Renovate PRs and merge-queue refs do not trigger the image workflow.

Consecutive master pushes share a cancellable group, so only the newest master request continues to update edge. Each X.Y.Z tag has its own cancellable release group, so a newer master build or a different release cannot cancel formal version publication. A single global group is intentionally avoided because GitHub retains only one pending run per group and could otherwise discard an intermediate release.

The single release job runs only when github.ref is refs/heads/master.

Accepted operating boundaries

The manifest step does not inspect the registry before publication and does not enforce tag immutability or stale-run ordering. A superseded master run can be canceled after platform digests are pushed but before they receive a manifest tag; registry cleanup is expected to remove those intermediates. Persistent storage growth, insufficient retention, or added registry cost would trigger a redesign of the cancellation boundary. These risks are accepted in favor of simple publication and rapid edge cancellation.

Validation

  • prek run --all-files
  • uv run --with pytest --with pytest-cov -- pytest --cov --cov-branch --cov-report=xml (648 passed)
  • targeted workflow validation through YAML, zizmor, and GitHub Actions lint hooks

@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The image workflow now validates publication refs, publishes immutable commit-based manifests with conditional release channels, and inspects those images directly. The release workflow rejects reused tags and atomically pushes release commits and tags. Documentation records the OCI publication rules.

Changes

OCI image publishing and release coordination

Layer / File(s) Summary
Atomic release commit and tag publication
.github/workflows/release.yml
Version updates are limited to selected files, existing release tags cause failure, and the branch plus tag are pushed atomically.
Image workflow triggers and publication gating
.github/workflows/image.yml
Tag pushes matching X.Y.Z, path filters, manual ref validation, serialized publishing, and repository checkout are added or updated.
Immutable manifest tagging and verification
.github/workflows/image.yml
The workflow publishes sha-<commit> manifests, conditionally assigns release, latest, and edge tags, validates digest artifacts, prevents unsafe overwrites, and scans the commit-specific image.
OCI release contract documentation
docs/requirements.md, docs/design.md, docs/notes.md
Documentation defines immutable image tags, release ref validation, atomic pushes, serialized manifest writes, and release-channel rules.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant GitRemote
  participant DigestArtifacts
  participant Registry
  GitHubActions->>GitRemote: Read release tags and master head
  GitHubActions->>DigestArtifacts: Validate platform digest filenames
  GitHubActions->>Registry: Inspect existing image tags
  GitHubActions->>Registry: Publish commit and conditional channel manifests
  GitHubActions->>Registry: Inspect and scan sha commit image
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: separating edge and release image channels.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-release-image-tags

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

qodo-code-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 31 rules

Grey Divider


Action required

1. Tag trigger too narrow ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new push.tags filter in image.yml uses regex-like syntax in a glob context, so it only
matches single-character components and will not trigger for valid multi-digit release tags like
1.10.0. As a result, some legitimate releases will never publish an X.Y.Z image tag.
Code

.github/workflows/image.yml[R8-9]

+    tags:
+      - "[0-9]+.[0-9]+.[0-9]+"
Relevance

⭐⭐⭐ High

Team accepts CI correctness fixes; prior workflow issues were fixed when flagged (PR #18).

PR-#18

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
image.yml uses a glob pattern that cannot express “one or more digits”; it matches only
single-character components. release.yml explicitly validates and creates X.Y.Z tags allowing
multi-digit components, so image.yml would miss some valid release tags and never publish their
versioned images.

.github/workflows/image.yml[4-12]
.github/workflows/release.yml[26-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`on.push.tags` in `.github/workflows/image.yml` is written like a regex, but GitHub Actions uses glob-style patterns there. The current pattern only matches tags with exactly one character per numeric component and will skip releases such as `1.10.0`.

## Issue Context
- Release workflow accepts multi-digit semver via regex validation and creates tags like `${WEATHER_BRIEFING_VERSION}`.
- Image workflow should run for any valid `X.Y.Z` release tag.

## Fix Focus Areas
- .github/workflows/image.yml[4-12]

## Suggested change
- Replace the tag filter with a glob that actually matches multi-digit components (e.g. `[0-9]*.[0-9]*.[0-9]*`), or broaden to `'*'` and rely on the existing runtime validation in the manifest step (`RELEASE_VERSION` regex check) to gate releases.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Manifest sources glob mismatch ✓ Resolved 🐞 Bug ≡ Correctness
Description
The multi-arch job now sets sources=("${REGISTRY_IMAGE}"@sha256:*), but the build job exports
digest artifacts as bare digest filenames (without the ${REGISTRY_IMAGE}@sha256: prefix). This
mismatch means the glob won’t expand to any sources, causing docker buildx imagetools create to
fail (or to receive a literal * pattern).
Code

.github/workflows/image.yml[R126-127]

+          sources=("${REGISTRY_IMAGE}"@sha256:*)
+          docker buildx imagetools create "${tags[@]}" "${sources[@]}"
Relevance

⭐⭐⭐ High

Similar imagetools manifest creation uses digests from '*' (PR #30); mismatch likely treated as
breaking CI bug.

PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow exports digest artifacts as plain digest filenames, but the new sources glob looks
for files prefixed with the registry image name, so it won’t match the downloaded artifacts and will
break manifest creation.

.github/workflows/image.yml[62-69]
.github/workflows/image.yml[100-127]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The manifest publishing step constructs its `sources` array using a pathname glob that expects filenames like `${REGISTRY_IMAGE}@sha256:<digest>`, but the workflow actually creates digest artifact files named only `<digest>`.

## Issue Context
- Digest export step touches files named `${digest#sha256:}`.
- Multiarch step should translate each filename into a source ref `${REGISTRY_IMAGE}@sha256:<filename>`.

## Fix Focus Areas
- .github/workflows/image.yml[62-69]
- .github/workflows/image.yml[100-127]

## Suggested change
Replace:
```bash
sources=("${REGISTRY_IMAGE}"@sha256:*)
docker buildx imagetools create "${tags[@]}" "${sources[@]}"
```
With something that derives sources from the actual filenames, e.g.:
```bash
sources=()
for d in *; do
 sources+=("${REGISTRY_IMAGE}@sha256:${d}")
done

docker buildx imagetools create "${tags[@]}" "${sources[@]}"
```
(or revert to the previous `printf "${REGISTRY_IMAGE}@sha256:%s " *` approach, but keep it array-safe to avoid word-splitting issues).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Release can run off-master ✓ Resolved 🐞 Bug ☼ Reliability
Description
.github/workflows/release.yml can be manually dispatched on any ref, but it unconditionally pushes
the checked-out HEAD to origin master and creates the release tag, so running it from a
non-master branch can publish and release unintended code.
Code

.github/workflows/release.yml[R82-83]

+          git tag -a "${WEATHER_BRIEFING_VERSION}" -m "Release ${WEATHER_BRIEFING_VERSION}"
+          git push --atomic origin HEAD:master "refs/tags/${WEATHER_BRIEFING_VERSION}"
Relevance

⭐⭐ Medium

Branch-guard suggestion existed but workflow merged unchanged in PR #30; no evidence team enforces
this guard yet.

PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow is manually dispatchable and uses a default checkout (selected ref), but later pushes
that checked-out HEAD directly to master along with the release tag; without a ref guard this
allows releasing from non-master refs.

.github/workflows/release.yml[4-22]
.github/workflows/release.yml[64-83]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The release workflow is `workflow_dispatch`-triggered and checks out the selected ref, but later pushes `HEAD:master` and a release tag without verifying that the run is actually on `refs/heads/master`. This can advance `master` (and trigger downstream image publication) from an unintended branch.

## Issue Context
Even with `--atomic`, `git push --atomic origin HEAD:master ...` updates `master` to whatever commit was checked out by the workflow run; it does not validate that the commit came from `master`.

## Fix Focus Areas
- .github/workflows/release.yml[4-22]
- .github/workflows/release.yml[64-83]

## Suggested fix
Add a hard guard early in the job (before modifying files / pushing) that fails unless the workflow is running on `refs/heads/master`.

Example (shell step near the top of the job):
```bash
if [[ "${GITHUB_REF}" != "refs/heads/master" ]]; then
 echo "Release workflow must run on master (got ${GITHUB_REF})"
 exit 1
fi
```
Optionally also verify `HEAD` equals the current remote `origin/master` before proceeding (fail if it’s behind/different), to prevent releasing an out-of-date checkout.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (4)
4. Git ls-remote errors ignored ✓ Resolved 🐞 Bug ☼ Reliability
Description
The latest_release and remote_master_sha computations run git ls-remote in pipelines without
pipefail/exit-status checks, so transient auth/network failures can silently produce empty values
and skip updating :latest/:edge even when they should be updated. This can violate the
documented contract for moving tags while still letting the workflow succeed and publish only
immutable tags.
Code

.github/workflows/image.yml[R191-207]

+            latest_release=$(git ls-remote --tags "https://github.com/${GITHUB_REPOSITORY}.git" \
+              | awk -F/ '$3 ~ /^[0-9]+\.[0-9]+\.[0-9]+$/ {print $3}' \
+              | sort -V \
+              | tail -n 1)
+            if [[ "${RELEASE_VERSION}" == "${latest_release}" ]]; then
+              tags+=(--tag "${REGISTRY_IMAGE}:latest")
+            else
+              echo "Skipping latest for superseded release ${RELEASE_VERSION}; newest tag is ${latest_release}"
+            fi
+          elif [[ "${GIT_REF}" == "refs/heads/master" ]]; then
+            remote_master_sha=$(git ls-remote --exit-code "https://github.com/${GITHUB_REPOSITORY}.git" \
+              refs/heads/master | awk '{print $1}')
+            if [[ "${GITHUB_SHA}" == "${remote_master_sha}" ]]; then
+              tags+=(--tag "${REGISTRY_IMAGE}:edge")
+            else
+              echo "Skipping edge for superseded master commit ${GITHUB_SHA}; current master is ${remote_master_sha}"
+            fi
Relevance

⭐⭐ Medium

No prior suggestions about git ls-remote pipeline error handling/pipefail in workflows; only
unrelated CI workflow feedback found (PR #18/#30).

PR-#18
PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow uses git ls-remote in pipelines whose exit status is determined by the last command,
so git ls-remote failures can be masked and yield empty values; the requirements explicitly state
that releases update latest and master updates edge.

.github/workflows/image.yml[191-207]
docs/requirements.md[59-61]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`git ls-remote` is executed in pipelines, but the step does not enable `set -o pipefail` or separately check `git ls-remote` exit codes. As a result, `git ls-remote` can fail while `awk/sort/tail` succeed, producing empty `latest_release`/`remote_master_sha` and causing the workflow to skip updating `latest`/`edge` without failing.

### Issue Context
This workflow is now the source of truth for moving tags (`latest` for newest release, `edge` for current master). Silent pipeline masking defeats that guarantee during transient GitHub/network issues.

### Fix Focus Areas
- .github/workflows/image.yml[191-207]

### Suggested fix
- Add `set -euo pipefail` (or at minimum `set -o pipefail`) near the top of the `run:` script.
- After computing `latest_release` / `remote_master_sha`, explicitly validate they are non-empty when required; if empty, print an error and `exit 1` to fail closed.
- Alternatively, capture `git ls-remote` output to a temp file/variable, check its exit status, then run `awk/sort/tail` on that output (avoids pipeline masking entirely).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Edge tag can regress ✓ Resolved 🐞 Bug ≡ Correctness
Description
Concurrency is keyed only by github.sha, so master builds for different commits can run
concurrently; an older run finishing later can re-tag :edge to an outdated manifest. This violates
the documented contract that edge tracks master (and can similarly affect latest if two
releases overlap).
Code

.github/workflows/image.yml[R22-24]

+concurrency:
+  group: image-publish-${{ github.sha }}
+  cancel-in-progress: false
Relevance

⭐⭐ Medium

No historical evidence on Actions concurrency/edge-tag race; only related image-tag workflow changes
in PR30.

PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
concurrency.group uses github.sha, which does not serialize different commits, while the publish
script conditionally applies the shared edge (and latest) tags; the requirements explicitly
state that edge must track master and latest only moves on releases.

.github/workflows/image.yml[22-24]
.github/workflows/image.yml[176-197]
docs/requirements.md[59-61]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow serializes runs by commit SHA, but it also writes mutable shared tags (`edge`, and `latest` on releases). Because different SHAs are allowed to publish in parallel, an older `master` build can finish after a newer one and move the `edge` tag backwards.

## Issue Context
The manifest publish step adds `:edge` when `GIT_REF` is `refs/heads/master`, but the workflow-level concurrency group is `image-publish-${{ github.sha }}`, so different `master` commits are not serialized.

## Fix Focus Areas
- .github/workflows/image.yml[22-24]
- .github/workflows/image.yml[176-197]

## Suggested fix
In the publish script, before adding the `:edge` tag, verify that `${GITHUB_SHA}` is still the current `master` HEAD (e.g., via `git ls-remote https://github.com/${GITHUB_REPOSITORY}.git refs/heads/master` and compare SHAs). If it’s not the HEAD anymore, skip tagging `edge` (still allow publishing/validating the immutable `sha-...` tag).

Optionally, if you also want to prevent `latest` regression under overlapping releases, serialize `latest` updates (or add a similar ordering guard) for release-tag runs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. SHA tag race rewrite ✓ Resolved 🐞 Bug ☼ Reliability
Description
The sha-${{ github.sha }} immutability guard is a TOCTOU check (inspect, then later push), so two
concurrent runs for the same SHA can both observe “tag missing” and then publish it, allowing the
supposedly immutable SHA tag to be moved. This is realistically triggered by the release workflow
pushing master and the semver tag separately, which can start two image workflow runs for the same
commit.
Code

.github/workflows/image.yml[R145-154]

+          tags=()
+          if inspect_tag "${COMMIT_TAG}" "${existing_manifest}" "${inspect_error}"; then
+            jq --sort-keys --compact-output . "${existing_manifest}" >"${existing_manifest_canonical}"
+            if ! cmp -s "${desired_manifest_canonical}" "${existing_manifest_canonical}"; then
+              echo "Refusing to move existing commit image ${REGISTRY_IMAGE}:${COMMIT_TAG}"
+              exit 1
+            fi
+          else
+            tags+=(--tag "${REGISTRY_IMAGE}:${COMMIT_TAG}")
+          fi
Relevance

⭐⭐ Medium

No historical evidence on concurrency/locking for immutable image tags; only generic workflow
bugfixes accepted/rejected (PR #18, #30).

PR-#18
PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
image.yml is triggered by both pushes to master and semver tag pushes, and the multiarch job
adds the SHA tag only when inspect_tag reports it absent; there is no locking/concurrency around
this publish. The release workflow pushes master and then the release tag, producing two separate
push events for the same commit SHA that can overlap and hit this race window.

.github/workflows/image.yml[4-17]
.github/workflows/image.yml[145-173]
.github/workflows/release.yml[82-84]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The SHA-tag immutability logic uses a non-atomic inspect-then-push sequence. When two workflow runs for the same `github.sha` overlap, both may decide the SHA tag does not exist and then both publish `sha-<commit>`, potentially moving an “immutable” tag.

## Issue Context
This can happen during releases because the release workflow pushes `HEAD:master` and then pushes the semver tag as separate operations, and `image.yml` triggers on both `push.branches: master` and `push.tags: <semver>`.

## Fix Focus Areas
- Add a `concurrency` gate keyed by commit SHA (recommended at least on `weather-briefing-multiarch`, optionally on the whole workflow) with `cancel-in-progress: false` so the second run waits rather than racing.
- Ensure the multiarch/tagging step is the serialized section (it’s the one that mutates tags).

### Suggested implementation sketch
- In `.github/workflows/image.yml`, add:
 - `concurrency:
     group: image-publish-${{ github.sha }}
     cancel-in-progress: false`
 either at the workflow top-level, or on the `weather-briefing-multiarch` job.

## Fix Focus Areas (exact locations)
- .github/workflows/image.yml[4-18]
- .github/workflows/image.yml[78-87]
- .github/workflows/image.yml[145-173]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Fail-open tag existence check ✓ Resolved 🐞 Bug ☼ Reliability
Description
The release-tag immutability guard treats any docker buildx imagetools inspect failure as "tag
does not exist", so transient auth/registry/network errors can let the job proceed and overwrite an
existing ${RELEASE_VERSION} tag. This breaks the documented requirement that X.Y.Z OCI tags must
be non-overwritable.
Code

.github/workflows/image.yml[R118-121]

+            if docker buildx imagetools inspect "${REGISTRY_IMAGE}:${RELEASE_VERSION}" >/dev/null 2>&1; then
+              echo "Refusing to overwrite existing release image ${REGISTRY_IMAGE}:${RELEASE_VERSION}"
+              exit 1
+            fi
Relevance

⭐⭐ Medium

No historical evidence on failing closed for imagetools inspect; workflow reliability suggestions
are mixed in PR #18.

PR-#18
PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow’s existence check suppresses all output and only blocks when inspect returns success,
meaning non-success (including non-"not found" errors) will fall through and proceed to publish
tags; this contradicts the stated policy that release tags are non-overwritable.

.github/workflows/image.yml[100-123]
docs/requirements.md[59-61]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The workflow uses `docker buildx imagetools inspect ... >/dev/null 2>&1` as an existence check and only blocks on exit code 0, which means *any* inspect error is treated like “not found”. This can allow overwriting an existing immutable release tag.

### Issue Context
You want `X.Y.Z` tags to be immutable. The workflow should only proceed to push a new `${RELEASE_VERSION}` tag when it can *positively confirm* the tag does not already exist; all other errors should abort.

### Fix Focus Areas
- .github/workflows/image.yml[100-123]

### Suggested fix approach
- Capture `imagetools inspect` stderr/stdout and exit code.
- If inspect succeeds (exit 0): tag exists → fail.
- If inspect fails: only continue when you can confidently identify a “not found” condition (e.g., match the known error message for missing manifest), otherwise fail with a clear error message (include the captured stderr).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

8. OCI限制缺少重评触发点 ✓ Resolved 📘 Rule violation ⚙ Maintainability ⭐ New
Description
docs/notes.md 新增的 OCI 标签发布说明明确接受了多个已知限制/风险(如不预查 registry、latest
并发更新),但未为这些“已知但未解决”的设计关注点补充明确的成立假设与可操作的重新评估触发条件。长期看会降低该发布策略在规模变化或事故后复盘时的可审计性与可维护性。
Code

docs/notes.md[R49-55]

+## OCI 标签作为发布通道
+
+镜像标签通道的规范以[运行环境要求](requirements.md#运行环境)为准。`master` 事件更新 `edge` 与 commit SHA 标签,版本 tag 事件更新同名版本、`latest` 与 commit SHA 标签;manifest 步骤不预查 registry,也不主动阻止标签被覆盖。
+
+release 工作流允许从手动选择的任意分支运行,并把生成的版本提交与同名 Git tag atomic push 到 `master`。这接受选错分支可能把非预期代码发布到 `master` 的风险。镜像工作流不提供手动发布或 merge queue 入口;Renovate 更新依赖 branch automerge 后直接触发 `master` 镜像构建,未合并的 Renovate PR 不构建镜像。
+
+连续的 `master` 事件共用可取消的 `edge` 构建组,使新提交尽早淘汰旧构建。每个版本 tag 使用独立的可取消构建组,避免后续 `master` 或其他版本取消正式发布;同一版本的重复事件只保留最新一次。不能让所有发布共用一个 concurrency group,因为 GitHub 只保留一个 pending run,短时间多个版本可能使中间版本未经构建即被替换。不同版本仍可能并发更新 `latest`,最终指向最后完成的版本构建,而不保证是数值最高的版本;这是简化发布逻辑所接受的边界。
Relevance

⭐⭐⭐ High

Team previously required documenting assumptions + reevaluation triggers for accepted design
concerns in notes (PR37).

PR-#37

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
合规规则要求:凡是文档中明确记录为“已知但暂不解决/接受”的设计关注点,必须同时写明成立假设与重新评估触发条件。当前新增段落描述了不预查 registry、允许覆盖、latest
并发竞态等接受的限制,但未提供对应的假设与触发点。

Rule 2141676: Document assumptions and reevaluation triggers for intentionally unresolved design concerns
docs/notes.md[49-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`docs/notes.md` 的 `## OCI 标签作为发布通道` 章节记录了已接受的发布限制/风险(例如不预查 registry、标签可被覆盖、`latest` 可能并发被不同版本更新),但没有像本文件其他章节那样补充“成立条件/假设”和“重新评估触发点”。这会使未来维护者无法明确在什么规模/事件下需要回头引入 tag 不可变性、排序/防回退机制或 registry 预查。

## Issue Context
合规要求:对“有意暂缓/接受的已知设计关注点”必须同时记录(1)显式假设与(2)至少一个具体可衡量的重评触发条件。

## Fix Focus Areas
- docs/notes.md[49-55]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. OCI tag policy duplicated ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
docs/notes.md restates the OCI image tag contract that is already documented in
docs/requirements.md, increasing the risk of drift between documents over time. Per the
documentation non-duplication rule, docs/notes.md should link to the canonical contract and focus
on rationale/trade-offs/boundaries only.
Code

docs/notes.md[R45-51]

+## OCI 标签作为发布身份
+
+版本标签和 commit SHA 标签用于回答“生产实际运行了哪一份源码”,因此必须保持不可变;`edge` 明确表示可随 `master` 移动的开发通道,`latest` 只随正式版本移动。发布工作流在推送前查询 registry:版本标签已存在时直接失败;SHA 标签已存在时只允许其 manifest 与本次构建完全一致,否则失败。registry 查询发生认证、网络或其他非“标签不存在”错误时同样失败,宁可延迟发布也不破坏身份映射。
+
+版本提交与同名 Git tag 使用一次 atomic push,避免任一 ref 单独前进形成半发布状态。手动镜像发布只接受 `master`,并在任何 registry 写入前验证 ref;merge queue 不运行镜像发布作业,避免生成没有 manifest 标签引用的平台 digest。
+
+该策略接受了 registry 暂时不可用时无法发布的代价,并假设 registry 的 manifest 查询能区分缺失与其他错误。若未来迁移 registry、启用服务端原生不可变标签或采用签名的 digest 发布,应重新评估客户端检查,但仍须保留 Git tag、包版本、OCI 版本标签和实际 manifest 之间的一一对应。
Relevance

⭐⭐⭐ High

PR37 defines notes.md as rationale-only, not restating formal contracts; duplicated OCI tag contract
likely rejected.

PR-#37
PR-#50

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The rule requires avoiding substantial duplication and linking to the canonical source.
docs/requirements.md defines the release tagging contract, and the newly added docs/notes.md
section repeats the same contract details instead of referencing the canonical requirements
location.

Rule 2141669: Avoid duplicating existing documentation; link to the canonical source instead
docs/requirements.md[59-61]
docs/notes.md[45-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`docs/notes.md` repeats the OCI image tag contract that is already stated in `docs/requirements.md`, which can cause the two to drift.

## Issue Context
`docs/requirements.md` appears to be the canonical contract location for runtime/release requirements, while `docs/notes.md` is intended for rationale and operating boundaries.

## Fix Focus Areas
- docs/notes.md[45-51]
- docs/requirements.md[59-61]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Partial release push window ✓ Resolved 🐞 Bug ☼ Reliability
Description
release.yml pushes HEAD:master before pushing the release tag, so if the tag push fails after
the branch push succeeds, master advances to the new version without the corresponding release
tag. This leaves a recoverable but inconsistent “half released” state until the tag is successfully
pushed.
Code

.github/workflows/release.yml[R82-84]

+          git tag -a "${WEATHER_BRIEFING_VERSION}" -m "Release ${WEATHER_BRIEFING_VERSION}"
          git push origin HEAD:master
+          git push origin "${WEATHER_BRIEFING_VERSION}"
Relevance

⭐⭐⭐ High

Repo previously pushed release tag before updating master (PR #30); team likely wants avoid
master-advanced-without-tag window.

PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow’s push order can advance master without a remote tag; semantic OCI tags are defined to
come from Git tags, so missing tags delay/complicate release publication.

.github/workflows/release.yml[77-84]
docs/requirements.md[59-61]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The release workflow can leave `master` updated but without the matching release tag if the tag push fails after the branch push.

### Issue Context
The image publishing contract is tag-driven for semantic `X.Y.Z` tags; delaying (or failing) the Git tag push delays (or prevents until retried) the release image publication.

### Fix Focus Areas
- .github/workflows/release.yml[77-84]

### Suggested fix
Reduce the partial-release window by pushing the tag before pushing `HEAD:master` (so failures block the branch update), or push both refs in a single `git push` invocation and add explicit retry/error messaging for the tag push (recognizing Git ref updates are not fully atomic).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (7)
11. Prerelease tag builds run ✓ Resolved 🐞 Bug ☼ Reliability
Description
The new push.tags glob also matches tags like 1.2.3-rc1, so the matrix build will push
per-platform digests, but the multi-arch job later rejects the tag as not X.Y.Z and fails, leaving
digest-only images and consuming CI/registry resources.
Code

.github/workflows/image.yml[R8-9]

+    tags:
+      - "[0-9]*.[0-9]*.[0-9]*"
Relevance

⭐⭐ Medium

No prior reviews about tag-glob strictness; team does accept workflow reliability fixes in general
(PR18).

PR-#18
PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow is configured to run on tag pushes matching a permissive glob, but the publish script
later enforces a strict X.Y.Z format and exits 1 on mismatch; meanwhile the matrix job pushes
images by digest during its build step, so invalid tags still produce pushed digests before failing.

.github/workflows/image.yml[4-16]
.github/workflows/image.yml[61-71]
.github/workflows/image.yml[192-206]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow now triggers on tag pushes that are not valid release tags (e.g. `1.2.3-rc1`). Those runs still execute the per-platform build job which pushes digest images, but then fail later in the multi-arch job when `RELEASE_VERSION` is validated as not matching `^[0-9]+\.[0-9]+\.[0-9]+$`. This causes wasted CI work and can leave registry artifacts that are not referenced by any human-facing tag.

## Issue Context
GitHub Actions `push.tags` uses glob matching, and the pattern `"[0-9]*.[0-9]*.[0-9]*"` allows suffixes after the patch digit because the final `*` can match `-rc1`.

## Fix Focus Areas
- .github/workflows/image.yml[4-16]
- .github/workflows/image.yml[39-96]
- .github/workflows/image.yml[117-206]

## Suggested fix
1. Add a small pre-validation job (or extend `validate-publish-ref`) that:
  - If `github.ref_type == 'tag'`, validates `${{ github.ref_name }}` with the same `X.Y.Z` regex used later.
  - Exposes an output like `publish_release=true/false`.
2. Gate **both** `weather-briefing` (matrix) and `weather-briefing-multiarch` jobs with `if:` so invalid tag refs do not build/push anything.
  - Example: `if: ${{ github.ref_type != 'tag' || needs.validate-publish-ref.outputs.valid_release == 'true' }}`
3. Keep the existing in-script validation as a defense-in-depth check, but avoid doing the expensive work for obviously invalid tag refs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Merge_group contends publish lock ✓ Resolved 🐞 Bug ☼ Reliability
Description
The workflow-level concurrency group is shared by merge_group events, but merge_group skips the
actual image build/publish jobs; these runs still execute validate-publish-ref and can delay an
in-flight publication or replace a pending publication run in the same concurrency group.
Code

.github/workflows/image.yml[R18-24]

env:
  WEATHER_BRIEFING_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/weather-briefing
-  WEATHER_BRIEFING_VERSION: "1.0.0"
+
+concurrency:
+  group: weather-briefing-image-publish
+  cancel-in-progress: false
Relevance

⭐⭐ Medium

No prior evidence about merge_group contending concurrency; workflow review history is mixed (PR #18
accepted+rejected).

PR-#18
PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow is triggered by merge_group and has a workflow-level concurrency group, but both
publishing jobs are explicitly skipped on merge_group; only the validation job remains, meaning
merge_group runs unnecessarily contend with the same concurrency lock used to serialize real
manifest/tag writes.

.github/workflows/image.yml[4-25]
.github/workflows/image.yml[26-45]
.github/workflows/image.yml[98-103]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`merge_group` workflow runs currently share the same workflow-level `concurrency.group` as real image publication runs, even though the publish jobs are skipped on `merge_group`. This creates unnecessary contention that can delay or displace pending publish runs.

### Issue Context
- `merge_group` triggers the workflow.
- Both `weather-briefing` and `weather-briefing-multiarch` are skipped when `github.event_name == 'merge_group'`.
- Workflow-level `concurrency` applies to the entire run, so even a short validation-only run participates in (and can hold) the shared lock.

### Fix Focus Areas
Choose one:
- Remove the `merge_group` trigger if no jobs should run for it.
- Or move `concurrency` down to the actual publishing job(s) (e.g., `weather-briefing-multiarch`) so `merge_group` runs do not contend.
- Or make the concurrency group event-specific so merge_group uses a different group.

- .github/workflows/image.yml[4-25]
- .github/workflows/image.yml[26-45]
- .github/workflows/image.yml[98-103]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Manifest compare order sensitive ✓ Resolved 🐞 Bug ☼ Reliability
Description
The commit-tag immutability guard compares jq --sort-keys JSON output, but it does not normalize
array ordering (e.g., a manifest list), so semantically equivalent manifests that differ only in
array order can fail the cmp check and block re-publishing sha-<commit> on reruns. This is a
fail-closed availability risk introduced by the new immutability check.
Code

.github/workflows/image.yml[R158-171]

+          desired_manifest=/tmp/weather-briefing-desired-manifest.json
+          desired_manifest_canonical=/tmp/weather-briefing-desired-manifest-canonical.json
+          existing_manifest=/tmp/weather-briefing-existing-manifest.json
+          existing_manifest_canonical=/tmp/weather-briefing-existing-manifest-canonical.json
+          inspect_error=/tmp/weather-briefing-inspect-error.txt
+          docker buildx imagetools create --dry-run "${sources[@]}" >"${desired_manifest}"
+          jq --sort-keys --compact-output . "${desired_manifest}" >"${desired_manifest_canonical}"
+
+          tags=()
+          if inspect_tag "${COMMIT_TAG}" "${existing_manifest}" "${inspect_error}"; then
+            jq --sort-keys --compact-output . "${existing_manifest}" >"${existing_manifest_canonical}"
+            if ! cmp -s "${desired_manifest_canonical}" "${existing_manifest_canonical}"; then
+              echo "Refusing to move existing commit image ${REGISTRY_IMAGE}:${COMMIT_TAG}"
+              exit 1
Relevance

⭐⭐ Medium

No historical reviews on manifest JSON canonicalization/array-order normalization; workflow review
history only shows unrelated suggestions (PR #18).

PR-#18

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code only sorts object keys and then does a byte-for-byte comparison; there is no normalization
of array ordering anywhere before cmp, so any ordering variance will be treated as a mismatch.

.github/workflows/image.yml[158-171]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The workflow canonicalizes manifests using `jq --sort-keys --compact-output`, then uses `cmp` to check equality. Sorting keys does not normalize array element order, so two equivalent manifest documents can compare unequal if arrays are emitted in different orders.

### Issue Context
This affects the `sha-<commit>` immutability/idempotency path. The workflow intends to allow re-runs when the existing commit tag points at the same multi-arch manifest.

### Fix Focus Areas
- .github/workflows/image.yml[158-171]

### Suggested fix
- Normalize the specific order-insensitive arrays before comparison, e.g.:
 - For OCI index: sort `.manifests` by a stable key such as `(.platform.os + "/" + .platform.architecture + ("/" + (.platform.variant // "")))` and/or `.digest`.
 - Apply the same transformation to both `desired_manifest` and `existing_manifest` before writing the canonical files.
- Alternatively, extract and compare a sorted list of `manifests[].digest` + platform tuples instead of comparing whole JSON blobs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


14. Edge tag from dispatch ✓ Resolved 🐞 Bug ≡ Correctness
Description
image.yml publishes edge whenever RELEASE_VERSION is empty, and workflow_dispatch can run on
non-master refs; a manual run from another branch can therefore move edge away from master.
This violates the documented contract that edge tracks master only.
Code

.github/workflows/image.yml[R155-172]

+          if [[ -n "${RELEASE_VERSION}" ]]; then
+            if ! [[ "${RELEASE_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+              echo "Release image tag must use X.Y.Z format"
+              exit 1
+            fi
+            package_version=$(python3 -c 'import os, tomllib; print(tomllib.load(open(os.path.join(os.environ["GITHUB_WORKSPACE"], "pyproject.toml"), "rb"))["project"]["version"])')
+            if [[ "${RELEASE_VERSION}" != "${package_version}" ]]; then
+              echo "Git tag ${RELEASE_VERSION} does not match package version ${package_version}"
+              exit 1
+            fi
+            if inspect_tag "${RELEASE_VERSION}" "${existing_manifest}" "${inspect_error}"; then
+              echo "Refusing to overwrite existing release image ${REGISTRY_IMAGE}:${RELEASE_VERSION}"
+              exit 1
+            fi
+            tags+=(--tag "${REGISTRY_IMAGE}:${RELEASE_VERSION}" --tag "${REGISTRY_IMAGE}:latest")
+          else
+            tags+=(--tag "${REGISTRY_IMAGE}:edge")
+          fi
Relevance

⭐⭐ Medium

Similar concern raised earlier: guard workflows because they can push HEAD:master; suggestion in PR
#30 was left undetermined.

PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow allows manual dispatch on arbitrary refs and tags edge for all non-tag runs; repo
docs explicitly state edge must only move with master.

.github/workflows/image.yml[4-18]
.github/workflows/image.yml[100-173]
docs/requirements.md[59-61]
docs/notes.md[37-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The image workflow can publish the mutable `edge` tag from a manually dispatched run on any branch (non-tag ref), moving `edge` away from `master`.

### Issue Context
`workflow_dispatch` does not restrict which ref can be selected, and the multiarch publishing script tags `edge` whenever `RELEASE_VERSION` is empty.

### Fix Focus Areas
- .github/workflows/image.yml[4-18]
- .github/workflows/image.yml[100-173]

### Suggested fix
Add a guard so that `edge` is only published when `github.ref == 'refs/heads/master'` (or equivalently when `github.ref_name == 'master'` and `github.ref_type == 'branch'`). For non-master `workflow_dispatch` runs, either fail fast (preferred for safety) or publish to a non-canonical tag (e.g. `manual-<sha>`), but do not update `edge`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


15. Merge_group pushes orphan digests ✓ Resolved 🐞 Bug ☼ Reliability
Description
On merge_group events the per-platform build job still pushes images by digest, but the
multi-arch/tagging job is skipped, so nothing in this workflow tags or references those pushed
digests. This can create ongoing registry churn (untagged/unreferenced objects) for merge-queue
runs.
Code

.github/workflows/image.yml[R79-83]

+    if: github.event_name != 'merge_group'
    runs-on: ubuntu-24.04
    needs:
      - weather-briefing
    steps:
Relevance

⭐⭐ Medium

No historical evidence on merge_group/registry churn handling; only recent workflow PRs (#30/#40)
lack merge_group discussion.

PR-#30
PR-#40

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow explicitly enables merge_group, always pushes by digest in the build job, and then
skips the job that would normally tag a manifest list.

.github/workflows/image.yml[16-18]
.github/workflows/image.yml[22-53]
.github/workflows/image.yml[78-83]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`merge_group` runs push per-platform images by digest but skip the multiarch/tagging job, leaving pushed artifacts unreferenced by any tag from this workflow.

### Issue Context
`weather-briefing` uses `push-by-digest=true` and `push=true` for every run, while `weather-briefing-multiarch` is disabled for `merge_group`.

### Fix Focus Areas
- .github/workflows/image.yml[16-18]
- .github/workflows/image.yml[22-53]
- .github/workflows/image.yml[78-83]

### Suggested fix
Either:
1) Add the same `if: github.event_name != 'merge_group'` gate to the `weather-briefing` job, or
2) Make the build step conditional (e.g. set `push: ${{ github.event_name != 'merge_group' }}` / use separate build configurations) so merge-queue builds don’t publish to the registry, while still allowing local build+test if desired.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


16. SHA tags can be rewritten ✓ Resolved 🐞 Bug ≡ Correctness
Description
The workflow always publishes sha-${{ github.sha }} without checking whether it already exists, so
reruns (or multiple runs for the same commit, e.g. branch push + tag push) can move the commit SHA
tag. This contradicts the documented policy that master builds publish an immutable commit SHA tag.
Code

.github/workflows/image.yml[R107-127]

+          tags=(--tag "${REGISTRY_IMAGE}:${COMMIT_TAG}")
+          if [[ -n "${RELEASE_VERSION}" ]]; then
+            if ! [[ "${RELEASE_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+              echo "Release image tag must use X.Y.Z format"
+              exit 1
+            fi
+            package_version=$(python3 -c 'import os, tomllib; print(tomllib.load(open(os.path.join(os.environ["GITHUB_WORKSPACE"], "pyproject.toml"), "rb"))["project"]["version"])')
+            if [[ "${RELEASE_VERSION}" != "${package_version}" ]]; then
+              echo "Git tag ${RELEASE_VERSION} does not match package version ${package_version}"
+              exit 1
+            fi
+            if docker buildx imagetools inspect "${REGISTRY_IMAGE}:${RELEASE_VERSION}" >/dev/null 2>&1; then
+              echo "Refusing to overwrite existing release image ${REGISTRY_IMAGE}:${RELEASE_VERSION}"
+              exit 1
+            fi
+            tags+=(--tag "${REGISTRY_IMAGE}:${RELEASE_VERSION}" --tag "${REGISTRY_IMAGE}:latest")
+          else
+            tags+=(--tag "${REGISTRY_IMAGE}:edge")
+          fi
+          sources=("${REGISTRY_IMAGE}"@sha256:*)
+          docker buildx imagetools create "${tags[@]}" "${sources[@]}"
Relevance

⭐⭐ Medium

No prior reviews about immutability of sha-* tags; only related image tagging changes in PR #30.

PR-#30

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The publish script unconditionally includes --tag ${REGISTRY_IMAGE}:sha-${github.sha} in the
manifest creation, and docs explicitly call the commit SHA tag immutable for master builds.

.github/workflows/image.yml[103-127]
docs/requirements.md[59-61]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The multi-arch publish step always tags `sha-${{ github.sha }}` and pushes it, with no guard against overwriting an existing tag.

### Issue Context
Docs state master builds should publish an *immutable* commit SHA tag. Without a pre-check, reruns or later workflows for the same SHA can update that tag.

### Fix Focus Areas
- .github/workflows/image.yml[103-127]

### Suggested fix approach
- Before `imagetools create`, check whether `${REGISTRY_IMAGE}:${COMMIT_TAG}` already exists.
- If it exists, either:
 - hard-fail to preserve immutability, or
 - verify it points to the exact same manifest and only allow a no-op re-push when identical.
- Ensure the check is fail-closed on registry errors (don’t treat errors as “missing”).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


17. Release tag policy duplicated ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The image tag immutability policy is described as a normative contract in both docs/design.md and
docs/requirements.md without linking to a single canonical source. This risks the two documents
drifting out of sync over time and violates the documentation non-duplication requirement.
Code

docs/requirements.md[61]

+目标为自有服务器。应用支持 Python 3.11–3.14,以当前最新稳定 Python 作为首选开发和测试版本,并在 CI 中覆盖全部受支持版本;项目元数据不设置未经验证的未来 Python 版本上限。项目使用 uv 原生 `uv_build` 构建后端并提交 `uv.lock`,不引入 PDM 工具链。Distroless Debian 13 镜像使用其系统 Python。应用以内置调度器常驻运行,SQLite 位于外部持久目录或卷中。项目维护单一、非 root、由 `uv.lock` 锁定依赖的 OCI 镜像,不使用 Docker Compose;GitHub Actions 仅用于 CI 和独立镜像发布,不预设生产运行 secrets。每次发布的 `X.Y.Z` OCI 标签必须由同名 Git tag 构建且不可覆盖,同时更新 `latest`;`master` 构建只能更新 `edge` 和不可变的 commit SHA 标签,不得修改 `latest` 或已发布版本。
Relevance

⭐⭐ Medium

No repo history enforcing “single canonical doc source”; docs edits in PRs didn’t flag duplication.

PR-#28
PR-#46
PR-#25

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2141669 requires avoiding duplicated documentation and linking to the canonical
reference. The new requirement sentence in docs/requirements.md restates the same policy also
added to docs/design.md, but neither points to the other as the source of truth.

Rule 2141669: Avoid duplicating existing documentation; link to the canonical source instead
docs/requirements.md[61-61]
docs/design.md[147-151]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The same release image tag policy is documented in multiple places (`docs/design.md` and `docs/requirements.md`) as if both are authoritative, without a clear canonical reference.

## Issue Context
This PR introduces/clarifies an important contract (immutable `sha-<commit>` tags, `edge` for master, `X.Y.Z` only from Git tags, `latest` only on releases). Per compliance, substantial conceptual docs should avoid duplication and instead point to a single source of truth.

## Fix Focus Areas
- docs/requirements.md[61-61]
- docs/design.md[147-151]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

18. Canceled runs strand digests 🐞 Bug ☼ Reliability ⭐ New
Description
With wo...

Comment thread .github/workflows/image.yml
Comment thread .github/workflows/image.yml Outdated
@IceCodeNew
IceCodeNew force-pushed the codex/fix-release-image-tags branch from 0b593a1 to 0ff7738 Compare July 17, 2026 02:50
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread docs/requirements.md Outdated
Comment thread .github/workflows/image.yml Outdated
Comment thread .github/workflows/image.yml Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 0ff7738

@IceCodeNew
IceCodeNew force-pushed the codex/fix-release-image-tags branch from 0ff7738 to e477b93 Compare July 17, 2026 03:12
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread .github/workflows/image.yml
Comment thread .github/workflows/image.yml Outdated
Comment thread .github/workflows/release.yml Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit e477b93

@IceCodeNew
IceCodeNew marked this pull request as ready for review July 17, 2026 03:23
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix: keep OCI release image tags immutable (edge/sha vs X.Y.Z/latest)

🐞 Bug fix ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Publish master builds only as edge and sha- tags.
• Publish X.Y.Z and update latest only from matching Git release tags.
• Refuse to overwrite existing registry tags; fail on version/tag mismatches.
Diagram

graph TD
  gh["GitHub push/tag event"] --> img["image.yml"] --> build["Build + digest artifacts"] --> publish["Manifest + tag policy"] -->|"inspect + push"| reg{{"Docker registry"}}
  rel["release.yml"] --> tag["Create X.Y.Z Git tag"] --> img
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Enable server-side immutable tags (registry policy)
  • ➕ Stronger guarantee enforced by the registry, not CI logic
  • ➕ Simpler workflows (no need to inspect manifests pre-push)
  • ➖ May not be available/consistent across registries (Docker Hub vs others)
  • ➖ Still needs client-side checks for Git tag ↔ package version mapping
2. Publish releases by digest only (no semantic tags)
  • ➕ Digest is inherently immutable and unambiguous
  • ➕ Avoids tag-collision and overwrite risks entirely
  • ➖ Worse UX for operators; harder to reason about deployed version
  • ➖ Conflicts with existing requirement to use X.Y.Z tags as identity
3. Add signed provenance/attestations (e.g., cosign/SLSA)
  • ➕ Improves supply-chain integrity beyond tag immutability
  • ➕ Allows verifying source/ref and build inputs for an image
  • ➖ More moving parts (keys/OIDC, policy, verification tooling)
  • ➖ Does not by itself prevent accidental tag reuse without additional policy

Recommendation: Current approach is appropriate given the stated requirements: enforce a clear tag channel split (edge vs X.Y.Z/latest) and add pre-push registry inspection to prevent rewriting identity tags. Consider a follow-up to adopt server-side immutability and/or signed provenance when registry capabilities and operational tooling are ready, but keep the Git tag ↔ package version ↔ OCI tag consistency checks either way.

Files changed (5) +91 / -16

Bug fix (2) +82 / -15
image.ymlEnforce immutable image tag policy and gate publishing by Git tags +77/-7

Enforce immutable image tag policy and gate publishing by Git tags

• Adds semantic-tag trigger ('[0-9]*.[0-9]*.[0-9]*') and removes the hard-coded version tag. Publishes 'edge' for master builds and 'sha-<commit>' for identity; on Git tags, publishes 'X.Y.Z' and updates 'latest'. Adds registry inspection logic to refuse overwriting existing release tags and to prevent moving an existing 'sha-*' tag unless the manifest is identical; switches scan/inspect steps to use the commit tag.

.github/workflows/image.yml

release.ymlStop rewriting image workflow versions; fail if release tag already exists +5/-8

Stop rewriting image workflow versions; fail if release tag already exists

• Removes the step that updated the image workflow’s version string and stops staging that file for commits. Changes release tagging behavior to error out if the Git tag already exists, then creates and pushes the annotated tag after pushing the version bump commit to master.

.github/workflows/release.yml

Documentation (3) +9 / -1
design.mdDocument registry immutability checks in the image workflow design +2/-0

Document registry immutability checks in the image workflow design

• Adds a design note that the image workflow implements release identity/tag channels and verifies immutable tags in the registry before building the multi-arch manifest.

docs/design.md

notes.mdAdd rationale for OCI tag immutability and failure policy +6/-0

Add rationale for OCI tag immutability and failure policy

• Introduces a detailed note explaining why 'sha-*' and 'X.Y.Z' tags must be immutable, why 'edge' is the moving channel, and why CI fails closed on registry inspection errors. Captures assumptions and future reevaluation points (registry migration, server-side immutability, digest/signature-based releases).

docs/notes.md

requirements.mdSpecify release vs master tagging rules for OCI publishing +1/-1

Specify release vs master tagging rules for OCI publishing

• Extends runtime requirements to mandate that 'X.Y.Z' images are built only from matching Git tags and cannot be overwritten, while master builds may only move 'edge' and create immutable 'sha-*' tags (no updates to 'latest' or release tags).

docs/requirements.md

Comment thread .github/workflows/image.yml Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit e477b93

@IceCodeNew
IceCodeNew force-pushed the codex/fix-release-image-tags branch from e477b93 to 07f545a Compare July 17, 2026 03:29
@IceCodeNew

IceCodeNew commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

已按最新 Qodo 意见逐项处理,并在 rebase 到 master@beb7c3d 后更新于 753eb2b

  1. 所有镜像 manifest 发布现在共用一个不取消的全局串行组,同 SHA 与跨 SHA 的 tag 写入都不会并发;
  2. 串行不依赖队列顺序:写 edge 前比较候选 SHA 与远端 master HEAD,旧 master 构建只保留不可变 SHA tag;
  3. 并发或乱序 release 也不会回退 latest:只有远端最高 X.Y.Z Git tag 对应的构建可更新它,旧 release 仍可发布自己的不可变版本 tag;
  4. release tag 与 latest 仍只由版本发布触发;workflow dispatch 有前置与最终 ref 双重校验;
  5. SHA/release tag 的 registry 检查保持失败关闭,并校验已存在 SHA manifest 的内容;
  6. requirements 作为标签通道契约的唯一来源,notes 只记录不可变身份、全局串行加远端 ref/version 校验的理由与取舍。

完整验证:633 passed,prek run --all-files 全部通过。

@IceCodeNew
IceCodeNew force-pushed the codex/fix-release-image-tags branch from 07f545a to 2383d4a Compare July 17, 2026 03:35
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread docs/notes.md Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 2383d4a

@IceCodeNew
IceCodeNew force-pushed the codex/fix-release-image-tags branch from 2383d4a to 2cc9d30 Compare July 17, 2026 04:05
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread .github/workflows/image.yml Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 2cc9d30

@IceCodeNew
IceCodeNew force-pushed the codex/fix-release-image-tags branch from 2cc9d30 to 753eb2b Compare July 17, 2026 04:20
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread .github/workflows/image.yml Outdated
Comment thread .github/workflows/image.yml Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 56a7fbd

@IceCodeNew
IceCodeNew force-pushed the codex/fix-release-image-tags branch from 56a7fbd to e27204c Compare July 17, 2026 06:25
@IceCodeNew IceCodeNew changed the title fix: keep release image tags immutable fix: separate edge and release image channels Jul 17, 2026
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread docs/notes.md Outdated
Comment thread .github/workflows/image.yml
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit e27204c

@IceCodeNew
IceCodeNew force-pushed the codex/fix-release-image-tags branch 2 times, most recently from a839ccf to 58f9f4c Compare July 17, 2026 06:36
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 58f9f4c

@IceCodeNew
IceCodeNew force-pushed the codex/fix-release-image-tags branch from 58f9f4c to aba68dc Compare July 17, 2026 06:51
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Review against the documented contract in this PR. In particular, workflow-level cancellation for superseded master builds is an explicit product requirement. The possibility of a cancellation after digest publication is intentionally accepted and documented with registry cleanup assumptions and concrete reevaluation triggers; do not suggest removing that required cancellation behavior unless a mitigation preserves the same semantics and the single-command manifest design.

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit aba68dc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant