From eae3fb7448f9b4c40c3ed8fd7f88bab77be25662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=AA=E6=B3=BD=E9=91=AB?= Date: Wed, 12 Aug 2026 23:26:48 +0800 Subject: [PATCH 1/5] ci: publish CHEK internal images Source sidechat: 019dc7df-46ed-71f0-84cf-0cb475d1857f --- .github/workflows/internal-image.yml | 60 +++++++++++++++++++ .../.openspec.yaml | 2 + .../publish-chek-internal-images/proposal.md | 23 +++++++ .../specs/github-automation/spec.md | 33 ++++++++++ .../publish-chek-internal-images/tasks.md | 13 ++++ 5 files changed, 131 insertions(+) create mode 100644 .github/workflows/internal-image.yml create mode 100644 openspec/changes/publish-chek-internal-images/.openspec.yaml create mode 100644 openspec/changes/publish-chek-internal-images/proposal.md create mode 100644 openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md create mode 100644 openspec/changes/publish-chek-internal-images/tasks.md diff --git a/.github/workflows/internal-image.yml b/.github/workflows/internal-image.yml new file mode 100644 index 0000000000..1386a63a60 --- /dev/null +++ b/.github/workflows/internal-image.yml @@ -0,0 +1,60 @@ +name: Internal Image + +on: + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + packages: write + +jobs: + publish: + name: Build and publish CHEK image + runs-on: ubuntu-24.04 + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + + - name: Set up QEMU + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c + + - name: Log in to GHCR + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract image metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha,prefix=sha-,format=short + type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }} + + - name: Build and push image + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a + with: + context: . + file: Dockerfile + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=codex-lb-internal + cache-to: type=gha,mode=max,scope=codex-lb-internal diff --git a/openspec/changes/publish-chek-internal-images/.openspec.yaml b/openspec/changes/publish-chek-internal-images/.openspec.yaml new file mode 100644 index 0000000000..5081c98763 --- /dev/null +++ b/openspec/changes/publish-chek-internal-images/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-12 diff --git a/openspec/changes/publish-chek-internal-images/proposal.md b/openspec/changes/publish-chek-internal-images/proposal.md new file mode 100644 index 0000000000..75338fd474 --- /dev/null +++ b/openspec/changes/publish-chek-internal-images/proposal.md @@ -0,0 +1,23 @@ +# Change: publish-chek-internal-images + +## Why + +CHEK deploys its maintained `chekdata/codex-lb` fork through ArgoCD. Production +must consume an image built from the reviewed fork commit rather than an +upstream image or a mutable local build, but the fork currently has no +repository-owned image publisher. + +## What Changes + +- Add a focused GitHub Actions workflow that publishes the fork to + `ghcr.io/chekdata/codex-lb` after a push to `main` or an explicit manual run. +- Publish both supported Linux architectures in one manifest. +- Publish an immutable short-SHA tag for GitOps and a mutable `main` convenience + tag while granting the workflow only read-content and write-package access. + +## Impact + +- Affected capability: `github-automation` +- Affected automation: `.github/workflows/internal-image.yml` +- Production manifests can pin a reviewed `sha-*` image without depending on + upstream publishing permissions or tags. diff --git a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md new file mode 100644 index 0000000000..ec64fa41f9 --- /dev/null +++ b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md @@ -0,0 +1,33 @@ +## ADDED Requirements + +### Requirement: CHEK fork publishes internally owned container images + +The `chekdata/codex-lb` repository SHALL build and publish a container image to +`ghcr.io/chekdata/codex-lb` after changes land on `main` and when an operator +explicitly dispatches the workflow. The published manifest MUST support +`linux/amd64` and `linux/arm64`, MUST include an immutable `sha-` +tag, and MAY update the `main` tag for operator convenience. Production GitOps +consumers MUST be able to select the immutable tag. + +The workflow MUST use pinned action revisions and MUST limit its repository +permissions to reading contents and writing packages. Pull request events MUST +NOT publish images. + +#### Scenario: Main commit produces an immutable multi-architecture image + +- **WHEN** a reviewed commit lands on `main` +- **THEN** the workflow publishes `ghcr.io/chekdata/codex-lb:sha-` +- **AND** the image manifest supports `linux/amd64` and `linux/arm64` +- **AND** the same run updates `ghcr.io/chekdata/codex-lb:main` + +#### Scenario: Pull request validation cannot publish a package + +- **WHEN** a pull request is opened or updated +- **THEN** the internal image workflow does not run from that event +- **AND** no image tag is published by the pull request + +#### Scenario: Operator can rebuild the current selected revision + +- **WHEN** an operator manually dispatches the workflow on an allowed ref +- **THEN** the workflow builds and publishes the selected commit with its + immutable short-SHA tag diff --git a/openspec/changes/publish-chek-internal-images/tasks.md b/openspec/changes/publish-chek-internal-images/tasks.md new file mode 100644 index 0000000000..6f397cdb24 --- /dev/null +++ b/openspec/changes/publish-chek-internal-images/tasks.md @@ -0,0 +1,13 @@ +# Tasks: publish-chek-internal-images + +## 1. Internal image publisher + +- [x] 1.1 Add a main-branch and manual-dispatch GHCR workflow with pinned actions +- [x] 1.2 Build and publish a multi-architecture image for amd64 and arm64 +- [x] 1.3 Emit immutable short-SHA and mutable main tags with minimal permissions + +## 2. Verification + +- [x] 2.1 Validate the workflow YAML and repository action policy +- [x] 2.2 Run strict OpenSpec validation +- [ ] 2.3 Merge the focused PR and verify the first main-branch image manifest From 2eb05f70a2ef5f65f1e995fffef41df2861b1822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=AA=E6=B3=BD=E9=91=AB?= Date: Wed, 12 Aug 2026 23:32:50 +0800 Subject: [PATCH 2/5] ci: use full SHA image tags Source sidechat: 019dc7df-46ed-71f0-84cf-0cb475d1857f --- .github/workflows/internal-image.yml | 2 +- openspec/changes/publish-chek-internal-images/proposal.md | 4 ++-- .../specs/github-automation/spec.md | 6 +++--- openspec/changes/publish-chek-internal-images/tasks.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/internal-image.yml b/.github/workflows/internal-image.yml index 1386a63a60..5d7bcf2ff3 100644 --- a/.github/workflows/internal-image.yml +++ b/.github/workflows/internal-image.yml @@ -44,7 +44,7 @@ jobs: with: images: ghcr.io/${{ github.repository }} tags: | - type=sha,prefix=sha-,format=short + type=sha,prefix=sha-,format=long type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }} - name: Build and push image diff --git a/openspec/changes/publish-chek-internal-images/proposal.md b/openspec/changes/publish-chek-internal-images/proposal.md index 75338fd474..7a3ea817c0 100644 --- a/openspec/changes/publish-chek-internal-images/proposal.md +++ b/openspec/changes/publish-chek-internal-images/proposal.md @@ -12,7 +12,7 @@ repository-owned image publisher. - Add a focused GitHub Actions workflow that publishes the fork to `ghcr.io/chekdata/codex-lb` after a push to `main` or an explicit manual run. - Publish both supported Linux architectures in one manifest. -- Publish an immutable short-SHA tag for GitOps and a mutable `main` convenience +- Publish an immutable full-SHA tag for GitOps and a mutable `main` convenience tag while granting the workflow only read-content and write-package access. ## Impact @@ -20,4 +20,4 @@ repository-owned image publisher. - Affected capability: `github-automation` - Affected automation: `.github/workflows/internal-image.yml` - Production manifests can pin a reviewed `sha-*` image without depending on - upstream publishing permissions or tags. + upstream publishing permissions or abbreviated-tag collisions. diff --git a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md index ec64fa41f9..da404e4302 100644 --- a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md +++ b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md @@ -5,7 +5,7 @@ The `chekdata/codex-lb` repository SHALL build and publish a container image to `ghcr.io/chekdata/codex-lb` after changes land on `main` and when an operator explicitly dispatches the workflow. The published manifest MUST support -`linux/amd64` and `linux/arm64`, MUST include an immutable `sha-` +`linux/amd64` and `linux/arm64`, MUST include an immutable `sha-` tag, and MAY update the `main` tag for operator convenience. Production GitOps consumers MUST be able to select the immutable tag. @@ -16,7 +16,7 @@ NOT publish images. #### Scenario: Main commit produces an immutable multi-architecture image - **WHEN** a reviewed commit lands on `main` -- **THEN** the workflow publishes `ghcr.io/chekdata/codex-lb:sha-` +- **THEN** the workflow publishes `ghcr.io/chekdata/codex-lb:sha-` - **AND** the image manifest supports `linux/amd64` and `linux/arm64` - **AND** the same run updates `ghcr.io/chekdata/codex-lb:main` @@ -30,4 +30,4 @@ NOT publish images. - **WHEN** an operator manually dispatches the workflow on an allowed ref - **THEN** the workflow builds and publishes the selected commit with its - immutable short-SHA tag + immutable full-SHA tag diff --git a/openspec/changes/publish-chek-internal-images/tasks.md b/openspec/changes/publish-chek-internal-images/tasks.md index 6f397cdb24..aa71a77ff7 100644 --- a/openspec/changes/publish-chek-internal-images/tasks.md +++ b/openspec/changes/publish-chek-internal-images/tasks.md @@ -4,7 +4,7 @@ - [x] 1.1 Add a main-branch and manual-dispatch GHCR workflow with pinned actions - [x] 1.2 Build and publish a multi-architecture image for amd64 and arm64 -- [x] 1.3 Emit immutable short-SHA and mutable main tags with minimal permissions +- [x] 1.3 Emit immutable full-SHA and mutable main tags with minimal permissions ## 2. Verification From 6ba20cb6dd4041ae1553a4df009d809380c23baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=AA=E6=B3=BD=E9=91=AB?= Date: Wed, 12 Aug 2026 23:42:26 +0800 Subject: [PATCH 3/5] ci: prevent immutable tag replacement Source sidechat: 019dc7df-46ed-71f0-84cf-0cb475d1857f --- .github/workflows/internal-image.yml | 14 ++++++++++++++ .../publish-chek-internal-images/proposal.md | 2 ++ .../specs/github-automation/spec.md | 19 +++++++++++++++++++ .../publish-chek-internal-images/tasks.md | 2 ++ 4 files changed, 37 insertions(+) diff --git a/.github/workflows/internal-image.yml b/.github/workflows/internal-image.yml index 5d7bcf2ff3..1f4a7f1c94 100644 --- a/.github/workflows/internal-image.yml +++ b/.github/workflows/internal-image.yml @@ -38,6 +38,20 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Refuse to overwrite immutable tag + env: + IMAGE_REF: ghcr.io/${{ github.repository }}:sha-${{ github.sha }} + run: | + if docker buildx imagetools inspect "${IMAGE_REF}" >/dev/null 2>inspect-error.log; then + echo "::error::Immutable image tag already exists: ${IMAGE_REF}" + exit 1 + fi + if ! grep -Eiq 'manifest unknown|name unknown|not found' inspect-error.log; then + cat inspect-error.log >&2 + echo "::error::Unable to prove immutable image tag is absent" + exit 1 + fi + - name: Extract image metadata id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 diff --git a/openspec/changes/publish-chek-internal-images/proposal.md b/openspec/changes/publish-chek-internal-images/proposal.md index 7a3ea817c0..496b3efb8a 100644 --- a/openspec/changes/publish-chek-internal-images/proposal.md +++ b/openspec/changes/publish-chek-internal-images/proposal.md @@ -14,6 +14,8 @@ repository-owned image publisher. - Publish both supported Linux architectures in one manifest. - Publish an immutable full-SHA tag for GitOps and a mutable `main` convenience tag while granting the workflow only read-content and write-package access. +- Fail closed before building when the full-SHA tag already exists or the + registry cannot prove that it is absent. ## Impact diff --git a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md index da404e4302..0b50c63297 100644 --- a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md +++ b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md @@ -9,6 +9,11 @@ explicitly dispatches the workflow. The published manifest MUST support tag, and MAY update the `main` tag for operator convenience. Production GitOps consumers MUST be able to select the immutable tag. +Before publishing, the workflow MUST prove that the full-SHA tag does not +already exist. It MUST fail without building when the tag exists and MUST fail +closed when a registry or network error prevents that absence check. It MUST +NOT overwrite a previously published full-SHA tag. + The workflow MUST use pinned action revisions and MUST limit its repository permissions to reading contents and writing packages. Pull request events MUST NOT publish images. @@ -29,5 +34,19 @@ NOT publish images. #### Scenario: Operator can rebuild the current selected revision - **WHEN** an operator manually dispatches the workflow on an allowed ref +- **AND** the selected commit's full-SHA tag does not yet exist - **THEN** the workflow builds and publishes the selected commit with its immutable full-SHA tag + +#### Scenario: Repeated publication cannot change an immutable tag + +- **GIVEN** the selected commit's full-SHA tag already exists in GHCR +- **WHEN** the workflow is rerun or manually dispatched for that commit +- **THEN** it fails before the image build +- **AND** the existing tag is not overwritten + +#### Scenario: Registry uncertainty fails closed + +- **GIVEN** the workflow cannot determine whether the full-SHA tag exists +- **WHEN** the pre-publish check encounters a registry or network error +- **THEN** the workflow fails without publishing diff --git a/openspec/changes/publish-chek-internal-images/tasks.md b/openspec/changes/publish-chek-internal-images/tasks.md index aa71a77ff7..7707aa8c06 100644 --- a/openspec/changes/publish-chek-internal-images/tasks.md +++ b/openspec/changes/publish-chek-internal-images/tasks.md @@ -5,6 +5,8 @@ - [x] 1.1 Add a main-branch and manual-dispatch GHCR workflow with pinned actions - [x] 1.2 Build and publish a multi-architecture image for amd64 and arm64 - [x] 1.3 Emit immutable full-SHA and mutable main tags with minimal permissions +- [x] 1.4 Refuse to overwrite an existing full-SHA tag and fail closed when its + registry state cannot be confirmed ## 2. Verification From 9cf3f27ec45e4c98fdfdb18a9cae0f2ab254a44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=AA=E6=B3=BD=E9=91=AB?= Date: Wed, 12 Aug 2026 23:47:34 +0800 Subject: [PATCH 4/5] ci: serialize image publication by commit Source sidechat: 019dc7df-46ed-71f0-84cf-0cb475d1857f --- .github/workflows/internal-image.yml | 4 ++-- .../changes/publish-chek-internal-images/proposal.md | 2 ++ .../specs/github-automation/spec.md | 12 +++++++++++- .../changes/publish-chek-internal-images/tasks.md | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/internal-image.yml b/.github/workflows/internal-image.yml index 1f4a7f1c94..1dc0c841fa 100644 --- a/.github/workflows/internal-image.yml +++ b/.github/workflows/internal-image.yml @@ -7,8 +7,8 @@ on: workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.sha }} + cancel-in-progress: false permissions: contents: read diff --git a/openspec/changes/publish-chek-internal-images/proposal.md b/openspec/changes/publish-chek-internal-images/proposal.md index 496b3efb8a..e7176356b1 100644 --- a/openspec/changes/publish-chek-internal-images/proposal.md +++ b/openspec/changes/publish-chek-internal-images/proposal.md @@ -16,6 +16,8 @@ repository-owned image publisher. tag while granting the workflow only read-content and write-package access. - Fail closed before building when the full-SHA tag already exists or the registry cannot prove that it is absent. +- Serialize all runs for the same commit across branch and tag aliases so the + absence check and first publication cannot race. ## Impact diff --git a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md index 0b50c63297..d504be1c5e 100644 --- a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md +++ b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md @@ -12,7 +12,9 @@ consumers MUST be able to select the immutable tag. Before publishing, the workflow MUST prove that the full-SHA tag does not already exist. It MUST fail without building when the tag exists and MUST fail closed when a registry or network error prevents that absence check. It MUST -NOT overwrite a previously published full-SHA tag. +NOT overwrite a previously published full-SHA tag. Runs selecting the same +commit through different branch or tag refs MUST be serialized by the full +commit SHA and MUST NOT cancel the run that currently owns publication. The workflow MUST use pinned action revisions and MUST limit its repository permissions to reading contents and writing packages. Pull request events MUST @@ -50,3 +52,11 @@ NOT publish images. - **GIVEN** the workflow cannot determine whether the full-SHA tag exists - **WHEN** the pre-publish check encounters a registry or network error - **THEN** the workflow fails without publishing + +#### Scenario: Branch and tag aliases cannot race publication + +- **GIVEN** a branch and a tag both select the same commit +- **WHEN** push and manual-dispatch runs overlap for those refs +- **THEN** the runs execute the absence check and publication serially +- **AND** the later run observes the tag created by the first run and fails + before building diff --git a/openspec/changes/publish-chek-internal-images/tasks.md b/openspec/changes/publish-chek-internal-images/tasks.md index 7707aa8c06..8e87fbac9c 100644 --- a/openspec/changes/publish-chek-internal-images/tasks.md +++ b/openspec/changes/publish-chek-internal-images/tasks.md @@ -7,6 +7,7 @@ - [x] 1.3 Emit immutable full-SHA and mutable main tags with minimal permissions - [x] 1.4 Refuse to overwrite an existing full-SHA tag and fail closed when its registry state cannot be confirmed +- [x] 1.5 Serialize publication by full commit SHA across branch and tag refs ## 2. Verification From 9d08d570629a98caf95818785c38d34e8a29f178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=AA=E6=B3=BD=E9=91=AB?= Date: Wed, 12 Aug 2026 23:52:35 +0800 Subject: [PATCH 5/5] ci: publish immutable image tags only Source sidechat: 019dc7df-46ed-71f0-84cf-0cb475d1857f --- .github/workflows/internal-image.yml | 1 - openspec/changes/publish-chek-internal-images/proposal.md | 4 ++-- .../specs/github-automation/spec.md | 6 +++--- openspec/changes/publish-chek-internal-images/tasks.md | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/internal-image.yml b/.github/workflows/internal-image.yml index 1dc0c841fa..5b3d507cdd 100644 --- a/.github/workflows/internal-image.yml +++ b/.github/workflows/internal-image.yml @@ -59,7 +59,6 @@ jobs: images: ghcr.io/${{ github.repository }} tags: | type=sha,prefix=sha-,format=long - type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }} - name: Build and push image uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a diff --git a/openspec/changes/publish-chek-internal-images/proposal.md b/openspec/changes/publish-chek-internal-images/proposal.md index e7176356b1..aa4729c0b3 100644 --- a/openspec/changes/publish-chek-internal-images/proposal.md +++ b/openspec/changes/publish-chek-internal-images/proposal.md @@ -12,8 +12,8 @@ repository-owned image publisher. - Add a focused GitHub Actions workflow that publishes the fork to `ghcr.io/chekdata/codex-lb` after a push to `main` or an explicit manual run. - Publish both supported Linux architectures in one manifest. -- Publish an immutable full-SHA tag for GitOps and a mutable `main` convenience - tag while granting the workflow only read-content and write-package access. +- Publish an immutable full-SHA tag for GitOps while granting the workflow only + read-content and write-package access; do not publish a mutable `main` tag. - Fail closed before building when the full-SHA tag already exists or the registry cannot prove that it is absent. - Serialize all runs for the same commit across branch and tag aliases so the diff --git a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md index d504be1c5e..2a9d0d3541 100644 --- a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md +++ b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md @@ -6,8 +6,8 @@ The `chekdata/codex-lb` repository SHALL build and publish a container image to `ghcr.io/chekdata/codex-lb` after changes land on `main` and when an operator explicitly dispatches the workflow. The published manifest MUST support `linux/amd64` and `linux/arm64`, MUST include an immutable `sha-` -tag, and MAY update the `main` tag for operator convenience. Production GitOps -consumers MUST be able to select the immutable tag. +tag, and MUST NOT publish a mutable `main` tag. Production GitOps consumers +MUST be able to select the immutable tag. Before publishing, the workflow MUST prove that the full-SHA tag does not already exist. It MUST fail without building when the tag exists and MUST fail @@ -25,7 +25,7 @@ NOT publish images. - **WHEN** a reviewed commit lands on `main` - **THEN** the workflow publishes `ghcr.io/chekdata/codex-lb:sha-` - **AND** the image manifest supports `linux/amd64` and `linux/arm64` -- **AND** the same run updates `ghcr.io/chekdata/codex-lb:main` +- **AND** the run does not publish a mutable `main` tag #### Scenario: Pull request validation cannot publish a package diff --git a/openspec/changes/publish-chek-internal-images/tasks.md b/openspec/changes/publish-chek-internal-images/tasks.md index 8e87fbac9c..2cdbc3c5b0 100644 --- a/openspec/changes/publish-chek-internal-images/tasks.md +++ b/openspec/changes/publish-chek-internal-images/tasks.md @@ -4,7 +4,7 @@ - [x] 1.1 Add a main-branch and manual-dispatch GHCR workflow with pinned actions - [x] 1.2 Build and publish a multi-architecture image for amd64 and arm64 -- [x] 1.3 Emit immutable full-SHA and mutable main tags with minimal permissions +- [x] 1.3 Emit only an immutable full-SHA tag with minimal permissions - [x] 1.4 Refuse to overwrite an existing full-SHA tag and fail closed when its registry state cannot be confirmed - [x] 1.5 Serialize publication by full commit SHA across branch and tag refs