Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/internal-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Internal Image

on:
push:
branches:
- main
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.sha }}
Comment thread
hongzexin marked this conversation as resolved.
cancel-in-progress: false

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: 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
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=sha,prefix=sha-,format=long

- name: Build and push image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
with:
context: .
file: Dockerfile
push: true
Comment thread
hongzexin marked this conversation as resolved.
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
2 changes: 2 additions & 0 deletions openspec/changes/publish-chek-internal-images/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-12
27 changes: 27 additions & 0 deletions openspec/changes/publish-chek-internal-images/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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 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
absence check and first publication cannot race.

## 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 abbreviated-tag collisions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
## 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-<full-commit>`
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
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.

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-<full-commit>`
- **AND** the image manifest supports `linux/amd64` and `linux/arm64`
- **AND** the run does not publish a mutable `main` tag

#### 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
- **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

#### 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
16 changes: 16 additions & 0 deletions openspec/changes/publish-chek-internal-images/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 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 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

## 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