From 7489e38638a3a4f76c65d715344cce58b6aa1e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=AA=E6=B3=BD=E9=91=AB?= Date: Thu, 13 Aug 2026 00:23:12 +0800 Subject: [PATCH 1/3] ci: mirror internal image to Beijing registry Source sidechat: 019dc7df-46ed-71f0-84cf-0cb475d1857f --- .github/workflows/internal-image.yml | 41 +++++++++++++------ .../publish-chek-internal-images/proposal.md | 14 ++++--- .../specs/github-automation/spec.md | 29 +++++++------ .../publish-chek-internal-images/tasks.md | 7 +++- 4 files changed, 59 insertions(+), 32 deletions(-) diff --git a/.github/workflows/internal-image.yml b/.github/workflows/internal-image.yml index 5b3d507cdd..22fe255850 100644 --- a/.github/workflows/internal-image.yml +++ b/.github/workflows/internal-image.yml @@ -38,25 +38,42 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Refuse to overwrite immutable tag + - name: Log in to CHEK Beijing registry + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f + with: + registry: ${{ secrets.REGISTRY_ENDPOINT }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Refuse to overwrite immutable tags env: - IMAGE_REF: ghcr.io/${{ github.repository }}:sha-${{ github.sha }} + GHCR_IMAGE_REF: ghcr.io/${{ github.repository }}:sha-${{ github.sha }} + BEIJING_IMAGE_REF: ${{ secrets.REGISTRY_ENDPOINT }}/prod/codex-lb: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 + check_absent() { + image_ref="$1" + error_log="$2" + if docker buildx imagetools inspect "${image_ref}" >/dev/null 2>"${error_log}"; then + echo "::error::Immutable image tag already exists: ${image_ref}" + exit 1 + fi + if ! grep -Eiq 'manifest unknown|name unknown|not found' "${error_log}"; then + cat "${error_log}" >&2 + echo "::error::Unable to prove immutable image tag is absent: ${image_ref}" + exit 1 + fi + } + + check_absent "${GHCR_IMAGE_REF}" ghcr-inspect-error.log + check_absent "${BEIJING_IMAGE_REF}" beijing-inspect-error.log - name: Extract image metadata id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 with: - images: ghcr.io/${{ github.repository }} + images: | + ghcr.io/${{ github.repository }} + ${{ secrets.REGISTRY_ENDPOINT }}/prod/codex-lb tags: | type=sha,prefix=sha-,format=long diff --git a/openspec/changes/publish-chek-internal-images/proposal.md b/openspec/changes/publish-chek-internal-images/proposal.md index aa4729c0b3..283449e25c 100644 --- a/openspec/changes/publish-chek-internal-images/proposal.md +++ b/openspec/changes/publish-chek-internal-images/proposal.md @@ -9,13 +9,14 @@ 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. +- Add a focused GitHub Actions workflow that publishes the fork to both + `ghcr.io/chekdata/codex-lb` and CHEK's Beijing production registry 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 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. +- Fail closed before building when either full-SHA tag already exists or either + registry cannot prove that its tag is absent. - Serialize all runs for the same commit across branch and tag aliases so the absence check and first publication cannot race. @@ -23,5 +24,6 @@ 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 abbreviated-tag collisions. +- Production manifests can pin a reviewed `sha-*` image from the in-region + registry without depending on cross-region pulls, 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 2a9d0d3541..eee3d0f8b6 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 @@ -2,19 +2,21 @@ ### 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 MUST NOT publish a mutable `main` tag. Production GitOps consumers -MUST be able to select the immutable tag. +The `chekdata/codex-lb` repository SHALL build and publish the same container +image to `ghcr.io/chekdata/codex-lb` and CHEK's Beijing production registry +after changes land on `main` and when an operator explicitly dispatches the +workflow. Each published manifest MUST support `linux/amd64` and +`linux/arm64`, MUST include an immutable `sha-` tag, and MUST NOT +publish a mutable `main` tag. Production GitOps consumers MUST be able to +select the immutable in-region 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. 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. +already exist in either registry. It MUST fail without building when either tag +exists and MUST fail closed when a registry or network error prevents either +absence check. It MUST 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 @@ -24,6 +26,7 @@ NOT publish images. - **WHEN** a reviewed commit lands on `main` - **THEN** the workflow publishes `ghcr.io/chekdata/codex-lb:sha-` +- **AND** it publishes the same tag to CHEK's Beijing production registry - **AND** the image manifest supports `linux/amd64` and `linux/arm64` - **AND** the run does not publish a mutable `main` tag @@ -42,14 +45,14 @@ NOT publish images. #### Scenario: Repeated publication cannot change an immutable tag -- **GIVEN** the selected commit's full-SHA tag already exists in GHCR +- **GIVEN** the selected commit's full-SHA tag already exists in either registry - **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 +- **GIVEN** the workflow cannot determine whether either 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 2cdbc3c5b0..470766acff 100644 --- a/openspec/changes/publish-chek-internal-images/tasks.md +++ b/openspec/changes/publish-chek-internal-images/tasks.md @@ -8,9 +8,14 @@ - [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 +- [x] 1.6 Publish the same immutable multi-architecture manifest to CHEK's + Beijing production registry +- [x] 1.7 Refuse publication when either registry tag exists or cannot be + proven absent ## 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 +- [x] 2.3 Merge the focused GHCR PR and verify the first main-branch image manifest +- [ ] 2.4 Merge the Beijing registry enhancement and verify both manifests From 7a47f6525bd0cb49810883d94d5c115f68ecaea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=AA=E6=B3=BD=E9=91=AB?= Date: Thu, 13 Aug 2026 00:26:17 +0800 Subject: [PATCH 2/3] ci: make Beijing mirror publication recoverable Source sidechat: 019dc7df-46ed-71f0-84cf-0cb475d1857f --- .github/workflows/internal-image.yml | 44 +++++++++++++------ .../publish-chek-internal-images/proposal.md | 5 ++- .../specs/github-automation/spec.md | 26 +++++++---- .../publish-chek-internal-images/tasks.md | 6 ++- 4 files changed, 56 insertions(+), 25 deletions(-) diff --git a/.github/workflows/internal-image.yml b/.github/workflows/internal-image.yml index 22fe255850..0ffcfb6ec4 100644 --- a/.github/workflows/internal-image.yml +++ b/.github/workflows/internal-image.yml @@ -45,39 +45,51 @@ jobs: username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - - name: Refuse to overwrite immutable tags + - name: Check immutable publication state + id: preflight env: GHCR_IMAGE_REF: ghcr.io/${{ github.repository }}:sha-${{ github.sha }} BEIJING_IMAGE_REF: ${{ secrets.REGISTRY_ENDPOINT }}/prod/codex-lb:sha-${{ github.sha }} run: | - check_absent() { + image_state() { image_ref="$1" error_log="$2" if docker buildx imagetools inspect "${image_ref}" >/dev/null 2>"${error_log}"; then - echo "::error::Immutable image tag already exists: ${image_ref}" - exit 1 + echo exists + return fi - if ! grep -Eiq 'manifest unknown|name unknown|not found' "${error_log}"; then - cat "${error_log}" >&2 - echo "::error::Unable to prove immutable image tag is absent: ${image_ref}" - exit 1 + if grep -Eiq 'manifest unknown|name unknown|not found' "${error_log}"; then + echo absent + return fi + cat "${error_log}" >&2 + echo "::error::Unable to determine immutable image state: ${image_ref}" + exit 1 } - check_absent "${GHCR_IMAGE_REF}" ghcr-inspect-error.log - check_absent "${BEIJING_IMAGE_REF}" beijing-inspect-error.log + ghcr_state=$(image_state "${GHCR_IMAGE_REF}" ghcr-inspect-error.log) + beijing_state=$(image_state "${BEIJING_IMAGE_REF}" beijing-inspect-error.log) + if [[ "${beijing_state}" == exists ]]; then + echo "::error::Immutable Beijing image tag already exists: ${BEIJING_IMAGE_REF}" + exit 1 + fi + if [[ "${ghcr_state}" == exists ]]; then + echo "Recovering the missing Beijing mirror from the existing immutable GHCR image" + echo "build_ghcr=false" >> "${GITHUB_OUTPUT}" + else + echo "build_ghcr=true" >> "${GITHUB_OUTPUT}" + fi - name: Extract image metadata id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 with: - images: | - ghcr.io/${{ github.repository }} - ${{ secrets.REGISTRY_ENDPOINT }}/prod/codex-lb + images: ghcr.io/${{ github.repository }} tags: | type=sha,prefix=sha-,format=long - name: Build and push image + if: steps.preflight.outputs.build_ghcr == 'true' uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: . @@ -88,3 +100,9 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=codex-lb-internal cache-to: type=gha,mode=max,scope=codex-lb-internal + + - name: Mirror immutable image to CHEK Beijing registry + env: + GHCR_IMAGE_REF: ghcr.io/${{ github.repository }}:sha-${{ github.sha }} + BEIJING_IMAGE_REF: ${{ secrets.REGISTRY_ENDPOINT }}/prod/codex-lb:sha-${{ github.sha }} + run: docker buildx imagetools create --tag "${BEIJING_IMAGE_REF}" "${GHCR_IMAGE_REF}" diff --git a/openspec/changes/publish-chek-internal-images/proposal.md b/openspec/changes/publish-chek-internal-images/proposal.md index 283449e25c..383a140871 100644 --- a/openspec/changes/publish-chek-internal-images/proposal.md +++ b/openspec/changes/publish-chek-internal-images/proposal.md @@ -15,8 +15,9 @@ repository-owned image publisher. - Publish both supported Linux architectures in one manifest. - 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 either full-SHA tag already exists or either - registry cannot prove that its tag is absent. +- Fail closed when the Beijing full-SHA tag already exists or either registry + state is uncertain, while allowing an interrupted publication to restore a + missing Beijing mirror from an existing immutable GHCR image. - Serialize all runs for the same commit across branch and tag aliases so the absence check and first publication cannot race. 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 eee3d0f8b6..8dde9e09fa 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 @@ -10,13 +10,15 @@ workflow. Each published manifest MUST support `linux/amd64` and publish a mutable `main` tag. Production GitOps consumers MUST be able to select the immutable in-region tag. -Before publishing, the workflow MUST prove that the full-SHA tag does not -already exist in either registry. It MUST fail without building when either tag -exists and MUST fail closed when a registry or network error prevents either -absence check. It MUST 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. +Before publishing, the workflow MUST determine the full-SHA tag state in both +registries and MUST fail closed when a registry or network error prevents that +check. It MUST fail without building when the Beijing tag already exists and +MUST NOT overwrite a previously published full-SHA tag. When GHCR already has +the selected immutable image but the Beijing tag is absent, the workflow MUST +recover the missing mirror from that GHCR image without rebuilding or changing +the GHCR 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 @@ -45,7 +47,7 @@ NOT publish images. #### Scenario: Repeated publication cannot change an immutable tag -- **GIVEN** the selected commit's full-SHA tag already exists in either registry +- **GIVEN** the selected commit's full-SHA tag already exists in the Beijing registry - **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 @@ -56,6 +58,14 @@ NOT publish images. - **WHEN** the pre-publish check encounters a registry or network error - **THEN** the workflow fails without publishing +#### Scenario: Interrupted publication can restore the in-region mirror + +- **GIVEN** the selected commit's immutable full-SHA tag exists in GHCR +- **AND** the Beijing full-SHA tag is absent +- **WHEN** the workflow is rerun for that commit +- **THEN** it copies the existing GHCR image to the Beijing registry +- **AND** it does not rebuild or overwrite the GHCR image + #### Scenario: Branch and tag aliases cannot race publication - **GIVEN** a branch and a tag both select the same commit diff --git a/openspec/changes/publish-chek-internal-images/tasks.md b/openspec/changes/publish-chek-internal-images/tasks.md index 470766acff..f373c48737 100644 --- a/openspec/changes/publish-chek-internal-images/tasks.md +++ b/openspec/changes/publish-chek-internal-images/tasks.md @@ -10,8 +10,10 @@ - [x] 1.5 Serialize publication by full commit SHA across branch and tag refs - [x] 1.6 Publish the same immutable multi-architecture manifest to CHEK's Beijing production registry -- [x] 1.7 Refuse publication when either registry tag exists or cannot be - proven absent +- [x] 1.7 Refuse publication when the Beijing registry tag exists or either + registry state cannot be determined +- [x] 1.8 Recover a missing Beijing mirror from the immutable GHCR source + without rebuilding or overwriting either tag ## 2. Verification From e3d89266aaf009934d97e6aec9c78c053d60096e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=AA=E6=B3=BD=E9=91=AB?= Date: Thu, 13 Aug 2026 00:32:43 +0800 Subject: [PATCH 3/3] ci: delegate Beijing mirror to private ops Source sidechat: 019dc7df-46ed-71f0-84cf-0cb475d1857f --- .github/workflows/internal-image.yml | 67 +++++++------------ .../publish-chek-internal-images/proposal.md | 16 ++--- .../specs/github-automation/spec.md | 65 +++++++++--------- .../publish-chek-internal-images/tasks.md | 11 ++- 4 files changed, 73 insertions(+), 86 deletions(-) diff --git a/.github/workflows/internal-image.yml b/.github/workflows/internal-image.yml index 0ffcfb6ec4..4a91609f39 100644 --- a/.github/workflows/internal-image.yml +++ b/.github/workflows/internal-image.yml @@ -38,46 +38,18 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Log in to CHEK Beijing registry - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f - with: - registry: ${{ secrets.REGISTRY_ENDPOINT }} - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - - name: Check immutable publication state - id: preflight + - name: Refuse to overwrite immutable tag env: - GHCR_IMAGE_REF: ghcr.io/${{ github.repository }}:sha-${{ github.sha }} - BEIJING_IMAGE_REF: ${{ secrets.REGISTRY_ENDPOINT }}/prod/codex-lb:sha-${{ github.sha }} + IMAGE_REF: ghcr.io/${{ github.repository }}:sha-${{ github.sha }} run: | - image_state() { - image_ref="$1" - error_log="$2" - if docker buildx imagetools inspect "${image_ref}" >/dev/null 2>"${error_log}"; then - echo exists - return - fi - if grep -Eiq 'manifest unknown|name unknown|not found' "${error_log}"; then - echo absent - return - fi - cat "${error_log}" >&2 - echo "::error::Unable to determine immutable image state: ${image_ref}" - exit 1 - } - - ghcr_state=$(image_state "${GHCR_IMAGE_REF}" ghcr-inspect-error.log) - beijing_state=$(image_state "${BEIJING_IMAGE_REF}" beijing-inspect-error.log) - if [[ "${beijing_state}" == exists ]]; then - echo "::error::Immutable Beijing image tag already exists: ${BEIJING_IMAGE_REF}" + 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 [[ "${ghcr_state}" == exists ]]; then - echo "Recovering the missing Beijing mirror from the existing immutable GHCR image" - echo "build_ghcr=false" >> "${GITHUB_OUTPUT}" - else - echo "build_ghcr=true" >> "${GITHUB_OUTPUT}" + 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 @@ -89,7 +61,7 @@ jobs: type=sha,prefix=sha-,format=long - name: Build and push image - if: steps.preflight.outputs.build_ghcr == 'true' + id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: . @@ -101,8 +73,21 @@ jobs: cache-from: type=gha,scope=codex-lb-internal cache-to: type=gha,mode=max,scope=codex-lb-internal - - name: Mirror immutable image to CHEK Beijing registry + - name: Request CHEK Beijing mirror env: - GHCR_IMAGE_REF: ghcr.io/${{ github.repository }}:sha-${{ github.sha }} - BEIJING_IMAGE_REF: ${{ secrets.REGISTRY_ENDPOINT }}/prod/codex-lb:sha-${{ github.sha }} - run: docker buildx imagetools create --tag "${BEIJING_IMAGE_REF}" "${GHCR_IMAGE_REF}" + GH_PAT: ${{ secrets.GH_PAT }} + IMAGE_DIGEST: ${{ steps.build.outputs.digest }} + run: | + test -n "${GH_PAT}" + test -n "${IMAGE_DIGEST}" + payload=$(jq -nc \ + --arg sha "${GITHUB_SHA}" \ + --arg digest "${IMAGE_DIGEST}" \ + '{event_type:"codex-lb-image-published",client_payload:{sha:$sha,digest:$digest}}') + curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_PAT}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/chekdata/ops-bootstrap/dispatches \ + --data "${payload}" diff --git a/openspec/changes/publish-chek-internal-images/proposal.md b/openspec/changes/publish-chek-internal-images/proposal.md index 383a140871..c8bb6673b5 100644 --- a/openspec/changes/publish-chek-internal-images/proposal.md +++ b/openspec/changes/publish-chek-internal-images/proposal.md @@ -9,15 +9,15 @@ repository-owned image publisher. ## What Changes -- Add a focused GitHub Actions workflow that publishes the fork to both - `ghcr.io/chekdata/codex-lb` and CHEK's Beijing production registry after a - push to `main` or an explicit manual run. +- Add a focused GitHub Actions workflow that publishes the fork to + `ghcr.io/chekdata/codex-lb`, then requests an authenticated mirror into + CHEK's Beijing production registry 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 while granting the workflow only read-content and write-package access; do not publish a mutable `main` tag. -- Fail closed when the Beijing full-SHA tag already exists or either registry - state is uncertain, while allowing an interrupted publication to restore a - missing Beijing mirror from an existing immutable GHCR image. +- Keep production registry credentials in the private operations repository; + the public fork sends only the reviewed commit SHA and built image digest. - Serialize all runs for the same commit across branch and tag aliases so the absence check and first publication cannot race. @@ -26,5 +26,5 @@ repository-owned image publisher. - Affected capability: `github-automation` - Affected automation: `.github/workflows/internal-image.yml` - Production manifests can pin a reviewed `sha-*` image from the in-region - registry without depending on cross-region pulls, upstream publishing - permissions, or abbreviated-tag collisions. + registry without giving its public source repository production registry + credentials or depending on cross-region runtime pulls. 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 8dde9e09fa..9b106c6abc 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 @@ -2,23 +2,26 @@ ### Requirement: CHEK fork publishes internally owned container images -The `chekdata/codex-lb` repository SHALL build and publish the same container -image to `ghcr.io/chekdata/codex-lb` and CHEK's Beijing production registry -after changes land on `main` and when an operator explicitly dispatches the -workflow. Each published manifest MUST support `linux/amd64` and -`linux/arm64`, MUST include an immutable `sha-` tag, and MUST NOT -publish a mutable `main` tag. Production GitOps consumers MUST be able to -select the immutable in-region tag. - -Before publishing, the workflow MUST determine the full-SHA tag state in both -registries and MUST fail closed when a registry or network error prevents that -check. It MUST fail without building when the Beijing tag already exists and -MUST NOT overwrite a previously published full-SHA tag. When GHCR already has -the selected immutable image but the Beijing tag is absent, the workflow MUST -recover the missing mirror from that GHCR image without rebuilding or changing -the GHCR 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 `chekdata/codex-lb` repository SHALL build and publish a container image to +`ghcr.io/chekdata/codex-lb`, then request an authenticated private operations +workflow to mirror that image into CHEK's Beijing production registry, after +changes land on `main` and when an operator explicitly dispatches the workflow. +Each published manifest MUST support `linux/amd64` and `linux/arm64`, MUST +include an immutable `sha-` tag, and MUST NOT publish a mutable +`main` tag. Production GitOps consumers MUST be able to select the immutable +in-region tag. + +Before publishing, the source workflow MUST prove that the GHCR full-SHA tag +does not already exist and MUST fail closed when a registry or network error +prevents that absence check. It MUST NOT overwrite a previously published +full-SHA tag. After the build succeeds, it MUST dispatch only the selected full +commit SHA and resulting image digest to the private operations repository; +production registry write credentials MUST NOT be configured in the public +fork. The private mirror workflow MUST verify that the selected commit belongs +to the `main` history and MUST use repository-scoped, temporary registry +credentials. 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 @@ -28,7 +31,8 @@ NOT publish images. - **WHEN** a reviewed commit lands on `main` - **THEN** the workflow publishes `ghcr.io/chekdata/codex-lb:sha-` -- **AND** it publishes the same tag to CHEK's Beijing production registry +- **AND** it requests the private operations workflow to mirror that exact SHA + and digest to CHEK's Beijing production registry - **AND** the image manifest supports `linux/amd64` and `linux/arm64` - **AND** the run does not publish a mutable `main` tag @@ -45,12 +49,19 @@ NOT publish images. - **THEN** the workflow builds and publishes the selected commit with its immutable full-SHA tag -#### Scenario: Repeated publication cannot change an immutable tag +#### Scenario: Production credentials stay private -- **GIVEN** the selected commit's full-SHA tag already exists in the Beijing registry -- **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 +- **WHEN** the public fork publishes a reviewed image +- **THEN** it sends only the full commit SHA and built digest to the private + operations repository +- **AND** the public fork does not receive production registry write credentials + +#### Scenario: Private mirror validates production provenance + +- **WHEN** the private operations workflow receives a mirror request +- **THEN** it verifies the commit belongs to the `chekdata/codex-lb` main history +- **AND** it verifies the GHCR tag resolves to the dispatched digest +- **AND** it uses temporary registry credentials scoped to `prod/codex-lb` #### Scenario: Registry uncertainty fails closed @@ -58,14 +69,6 @@ NOT publish images. - **WHEN** the pre-publish check encounters a registry or network error - **THEN** the workflow fails without publishing -#### Scenario: Interrupted publication can restore the in-region mirror - -- **GIVEN** the selected commit's immutable full-SHA tag exists in GHCR -- **AND** the Beijing full-SHA tag is absent -- **WHEN** the workflow is rerun for that commit -- **THEN** it copies the existing GHCR image to the Beijing registry -- **AND** it does not rebuild or overwrite the GHCR image - #### Scenario: Branch and tag aliases cannot race publication - **GIVEN** a branch and a tag both select the same commit diff --git a/openspec/changes/publish-chek-internal-images/tasks.md b/openspec/changes/publish-chek-internal-images/tasks.md index f373c48737..bd214ff1e0 100644 --- a/openspec/changes/publish-chek-internal-images/tasks.md +++ b/openspec/changes/publish-chek-internal-images/tasks.md @@ -8,12 +8,11 @@ - [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 -- [x] 1.6 Publish the same immutable multi-architecture manifest to CHEK's - Beijing production registry -- [x] 1.7 Refuse publication when the Beijing registry tag exists or either - registry state cannot be determined -- [x] 1.8 Recover a missing Beijing mirror from the immutable GHCR source - without rebuilding or overwriting either tag +- [x] 1.6 Request a private operations workflow to mirror the reviewed GHCR + SHA and digest into CHEK's Beijing production registry +- [x] 1.7 Keep production registry credentials out of the public fork +- [x] 1.8 Require main-history provenance and scoped temporary credentials in + the private mirror workflow ## 2. Verification