From 0b7e7c74e8a80e2ce5c43b5c4043d9e9b67f7fcb Mon Sep 17 00:00:00 2001 From: samunohito <46447427+samunohito@users.noreply.github.com> Date: Fri, 6 Nov 2026 19:51:56 +0900 Subject: [PATCH 1/2] ci: verify release images before pushing the tested IDs --- .github/actions/test-docker-image/action.yml | 60 +++++++++++++++++ .github/workflows/ci.yml | 32 ++------- .github/workflows/docker.yml | 65 ++++++++++++++----- README.md | 5 ++ .../2026-11-06-verified-docker-release.md | 38 +++++++++++ 5 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 .github/actions/test-docker-image/action.yml create mode 100644 docs/superpowers/plans/2026-11-06-verified-docker-release.md diff --git a/.github/actions/test-docker-image/action.yml b/.github/actions/test-docker-image/action.yml new file mode 100644 index 0000000..fa32bd6 --- /dev/null +++ b/.github/actions/test-docker-image/action.yml @@ -0,0 +1,60 @@ +name: Test Docker image +description: Verify a loaded image with its built-in healthcheck and real image inference + +inputs: + image: + description: Locally loaded Docker image to verify + required: true + +outputs: + image-id: + description: Immutable ID of the image used by the verified container + value: ${{ steps.image.outputs.id }} + +runs: + using: composite + steps: + # The host only runs the HTTP checker. The server and model stay inside Docker. + - uses: pnpm/action-setup@v6.1.0 + - uses: actions/setup-node@v7.1.0 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + shell: bash + - name: Resolve immutable image + id: image + shell: bash + env: + TEST_IMAGE: ${{ inputs.image }} + run: | + image_id=$(timeout --signal=KILL 60s docker image inspect "$TEST_IMAGE" --format '{{.Id}}') + [[ "$image_id" =~ ^sha256:[0-9a-f]{64}$ ]] + echo "id=$image_id" >> "$GITHUB_OUTPUT" + - name: Start built image + shell: bash + env: + TESTED_IMAGE_ID: ${{ steps.image.outputs.id }} + SENSITIVE_DETECTOR_API_KEY: docker-e2e-test-key + run: | + timeout --signal=KILL 60s docker create --name sensitive-detector-e2e \ + --publish 127.0.0.1::3009 \ + --mount "type=bind,source=$GITHUB_WORKSPACE/config.docker.mjs,target=/config/config.mjs,readonly" \ + --env SENSITIVE_DETECTOR_API_KEY \ + "$TESTED_IMAGE_ID" + timeout --signal=KILL 60s docker start sensitive-detector-e2e + - name: Check health, authentication and image predictions + shell: bash + env: + SENSITIVE_DETECTOR_API_KEY: docker-e2e-test-key + run: pnpm test:e2e:docker sensitive-detector-e2e + - name: Container diagnostics + if: failure() + shell: bash + run: | + timeout --signal=KILL 60s docker logs sensitive-detector-e2e || true + timeout --signal=KILL 60s docker inspect sensitive-detector-e2e || true + - name: Remove test container + if: always() + shell: bash + run: timeout --signal=KILL 60s docker rm --force sensitive-detector-e2e diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c11e21..fb6a3cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,17 +48,8 @@ jobs: runner: ubuntu-24.04-arm env: TEST_IMAGE: sensitive-detector:e2e - TEST_CONTAINER: sensitive-detector-e2e - SENSITIVE_DETECTOR_API_KEY: docker-e2e-test-key steps: - uses: actions/checkout@v7.0.1 - # The host only runs the HTTP checker. Build the server inside Docker. - - uses: pnpm/action-setup@v6.1.0 - - uses: actions/setup-node@v7.1.0 - with: - node-version: 22 - cache: pnpm - - run: pnpm install --frozen-lockfile - uses: docker/setup-buildx-action@v4 - name: Build and load image uses: docker/build-push-action@v7 @@ -70,22 +61,7 @@ jobs: tags: ${{ env.TEST_IMAGE }} cache-from: type=gha,scope=docker-e2e-${{ matrix.arch }} cache-to: type=gha,mode=max,scope=docker-e2e-${{ matrix.arch }} - - name: Start built image - run: | - timeout --signal=KILL 60s docker create --name "$TEST_CONTAINER" \ - --publish 127.0.0.1::3009 \ - --mount "type=bind,source=$GITHUB_WORKSPACE/config.docker.mjs,target=/config/config.mjs,readonly" \ - --env SENSITIVE_DETECTOR_API_KEY \ - "$TEST_IMAGE" - timeout --signal=KILL 60s docker start "$TEST_CONTAINER" - - name: Check health, authentication and image predictions - run: pnpm test:e2e:docker "$TEST_CONTAINER" - - name: Container diagnostics - if: failure() - run: | - timeout --signal=KILL 60s docker logs "$TEST_CONTAINER" || true - timeout --signal=KILL 60s docker inspect "$TEST_CONTAINER" || true - - name: Remove test container - if: always() - run: | - timeout --signal=KILL 60s docker rm --force "$TEST_CONTAINER" + - name: Verify built image + uses: ./.github/actions/test-docker-image + with: + image: ${{ env.TEST_IMAGE }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7a3ff21..d2467f7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -18,14 +18,21 @@ env: jobs: # see https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners build: - name: Build - runs-on: ubuntu-latest + name: Build, verify and push (${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + env: + TEST_IMAGE: sensitive-detector:release-test strategy: fail-fast: false matrix: - platform: - - linux/amd64 - - linux/arm64 + include: + - arch: amd64 + platform: linux/amd64 + runner: ubuntu-24.04 + - arch: arm64 + platform: linux/arm64 + runner: ubuntu-24.04-arm if: github.repository == 'misskey-dev/sensitive-detector' steps: - name: Prepare @@ -34,6 +41,8 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo uses: actions/checkout@v7.0.1 + with: + ref: ${{ github.sha }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Docker meta @@ -42,28 +51,48 @@ jobs: with: images: ${{ env.REGISTRY_IMAGE }} tags: ${{ env.TAGS }} - - name: Log in to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push by digest - id: build + - name: Build and load image uses: docker/build-push-action@v7 with: context: . - push: true + load: true + push: false + tags: ${{ env.TEST_IMAGE }} platforms: ${{ matrix.platform }} provenance: false labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=gha,scope=docker-release-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=docker-release-${{ matrix.arch }} + - name: Verify built image + id: verify + uses: ./.github/actions/test-docker-image + with: + image: ${{ env.TEST_IMAGE }} + - name: Log in to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # Push the verified ID directly: a second build could produce a different image. + - name: Push verified image + id: push + env: + TESTED_IMAGE_ID: ${{ steps.verify.outputs.image-id }} + run: | + publish_ref="$REGISTRY_IMAGE:build-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$PLATFORM_PAIR" + timeout --signal=KILL 60s docker tag "$TESTED_IMAGE_ID" "$publish_ref" + timeout --signal=KILL 600s docker push "$publish_ref" + repo_digest=$(timeout --signal=KILL 60s docker image inspect "$publish_ref" --format '{{json .RepoDigests}}' | + jq -er --arg prefix "$REGISTRY_IMAGE@" \ + 'map(select(startswith($prefix))) | unique | if length == 1 then .[0] else error("expected one registry digest") end') + digest="${repo_digest#*@}" + [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] + echo "digest=$digest" >> "$GITHUB_OUTPUT" - name: Export digest run: | mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" + digest="${{ steps.push.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest uses: actions/upload-artifact@v7 diff --git a/README.md b/README.md index c74fb91..27e3af0 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,11 @@ CIの `Docker (amd64)` / `Docker (arm64)` ジョブは、それぞれ `ubuntu-24 各HTTP通信は本文を含め最大60秒。失敗時はログ・状態を出力し、終了時はコンテナを削除する。 PR、mainへのpush、手動実行が対象で、レジストリへのログイン・pushは行わない。 +リリース時も同じDocker検証actionを実行し、成功したコンテナが使用したimage IDを +再ビルドせずGHCRにpushする。amd64・arm64の両方が成功してから、従来のsemverタグの +manifestを公開する。manifestへの受け渡し用に、実行ID・再実行番号・architectureを含む +`build---linux-`補助タグもGHCRに保持する。 + Dockerの検証はローカルでも実行できる(Node.js 22以上、GNU `timeout` と `pnpm install` が必要)。 検証コマンドは既存コンテナを確認するため、呼び出し側で削除を行う。 diff --git a/docs/superpowers/plans/2026-11-06-verified-docker-release.md b/docs/superpowers/plans/2026-11-06-verified-docker-release.md new file mode 100644 index 0000000..d94419b --- /dev/null +++ b/docs/superpowers/plans/2026-11-06-verified-docker-release.md @@ -0,0 +1,38 @@ +# Verified Docker Release Implementation Plan + +> **For agentic workers:** Use superpowers:executing-plans. The user requested implementing release-time verification using the existing Docker checks. + +**Goal:** リリース時も実際に検証したイメージだけを公開する。 +**Architecture:** native amd64/arm64でbuild/loadし、CIと共通のcomposite actionで検証する。actionが検証した不変のimage IDを出力し、そのIDをtag/pushする。再ビルドしない。両architectureの成功後、公開先のdigestから既存のsemver manifestを作る。 +**Spec:** この会話の「リリースworkflowでも同じ検証を通してからpush」。#41はマージ済み。 +**Decision:** キャッシュを使う再ビルドより、検証したimage IDの直接pushを選ぶ。同一成果物であることが明確。OCI転送ツールの追加は避け、既存Docker CLIを使う。受け渡し用に`build---linux-`補助タグがGHCRに残る。ユーザー向けsemverタグの規則は維持する。 + +## Constraints + +- amd64=`ubuntu-24.04`、arm64=`ubuntu-24.04-arm`。configだけをreadonly mountし、モデルはimage同梱。 +- HEALTHCHECK、health200、unauthenticated401、固定画像Neutral/Sexyの推論を必須とする。 +- 起動120秒、HTTP60秒、Docker管理操作60秒。pushは大きなlayer転送を考慮して最大600秒。 +- 検証失敗時はlogin/push/digest exportを実行しない。ログ・状態を出力し、コンテナ削除を試行する。 +- 本番レジストリへの実pushやrelease作成は、この実装作業では行わない。 +- 公開API・モデル判定・fixtureは変更しない。 + +## Tasks + +- [x] Baseline165testsを確認する。 +- [x] `.github/actions/test-docker-image/action.yml`に既存のDocker lifecycle/host setupを移し、immutable image IDで起動・出力する。CIはこのactionを使う。 +- [x] releaseworkflowをnativebuild/load→共通検証→login→同じimage IDのpush→registry digest exportに変更する。既存mergeは両方の成功を待つ。 +- [x] 実際のaction/stepのshellを使い、localregistryでtest/push/manifestを検証し、registry manifestのconfig digestが検証済みimage IDに一致することを確認する。 +- [x] 検証失敗時のpush阻止とcleanup、異常digest拒否をfault injectionで確認する。actionlint・全テスト・型・lint・serverE2Eを実行する。 +- [x] 独立エージェントの敵対的レビューで実質的な指摘がなくなるまで修正・再レビューする。 + +**PR verification:** 共通actionを使うamd64/arm64を含むCI全5ジョブの最終結果は、PR本文とChecksに記録する。 + +## Review Focus + +- testとpushの間で再ビルドや可変tagの再解決をしない。 +- composite内部のfailureがcleanup成功によって握りつぶされない。 +- release tagをcheckoutし、誤ったbranchのコードを公開しない。 +- digestがローカルimage IDではなく公開先のmanifest digestであること。 +- 片方が失敗した場合、semver manifestの公開を行わない。 + +**Review result:** checkoutの未修飾タグ名が同名branchを選び得る問題を公式helperで再現し、`github.sha`へ固定して解消した。再レビューで残る実質的な指摘なし。CodeRabbitも共通actionを含む全5ファイルをレビューし指摘なし。 From 67a65df54c369070b26d182c473c780164f80103 Mon Sep 17 00:00:00 2001 From: samunohito <46447427+samunohito@users.noreply.github.com> Date: Fri, 6 Nov 2026 20:01:46 +0900 Subject: [PATCH 2/2] docs: remove Superpowers planning documents --- .../superpowers/plans/2026-11-06-docker-ci.md | 67 ------------------- .../plans/2026-11-06-server-e2e.md | 49 -------------- .../2026-11-06-verified-docker-release.md | 38 ----------- 3 files changed, 154 deletions(-) delete mode 100644 docs/superpowers/plans/2026-11-06-docker-ci.md delete mode 100644 docs/superpowers/plans/2026-11-06-server-e2e.md delete mode 100644 docs/superpowers/plans/2026-11-06-verified-docker-release.md diff --git a/docs/superpowers/plans/2026-11-06-docker-ci.md b/docs/superpowers/plans/2026-11-06-docker-ci.md deleted file mode 100644 index 7d4710b..0000000 --- a/docs/superpowers/plans/2026-11-06-docker-ci.md +++ /dev/null @@ -1,67 +0,0 @@ -# Docker CI Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** PR時にamd64・arm64のDockerビルドと、ビルドしたイメージ自身の起動・画像推論を検証する。 - -**Architecture:** CIの独立matrixジョブがイメージをローカルにロードし、既存configだけを読み取り専用でマウントする。TypeScriptの検証コマンドがDockerのHEALTHCHECKと公開ポートを確認し、#38の画像・検証処理を再利用する。診断と削除はworkflowの独立した終了ステップで行う。 - -**Tech Stack:** GitHub Actions, Docker Buildx, Node.js 22, TypeScript/tsx, Vitest. - -**Spec:** https://github.com/misskey-dev/sensitive-detector/issues/39 (会話で合意済みのIssue 2) - -## Global Constraints - -- amd64は`ubuntu-24.04`、arm64は`ubuntu-24.04-arm`。QEMUは使用しない。 -- PR、mainへのpush、workflow_dispatchで実行する。 -- 起動待ちは最大120秒、HTTPリクエストは応答本文を含め最大60秒。タイムアウトは失敗する。 -- モデルはイメージ同梱。既存`config.docker.mjs`のみを`/config/config.mjs`にreadonlyでマウントし、テスト用APIキーを環境変数で渡す。 -- `/health`の200、組み込みHEALTHCHECKのhealthy、実画像の期待予測、認証なし401を全て必須とする。 -- 失敗時はコンテナログと状態を出力し、成功・失敗・キャンセル時とも削除ステップを実行する。 -- レジストリへのログイン・push、公開API、本番の判定仕様、リリースworkflowは変更しない。 - -## Review Focus - -- healthyにならない・早期終了・HEALTHCHECK欠落を成功と誤認しない。 -- 応答ヘッダーのみ到着し本文が止まったHTTPも時間制限を守る。 -- 認証不要になった推論APIや異なる予測を成功にしない。 -- build/start/checkのどこで失敗しても診断・削除ステップが実行される。 -- 各runnerでビルドしたイメージ自身を起動し、ホストのモデルで同梱漏れを隠さない。 - -### Task 1: Docker readiness and HTTP verification - -**Files:** -- Create: `apps/server/e2e/check-docker.ts`, `apps/server/e2e/run-docker.ts` -- Test: `apps/server/test/e2e-docker.test.ts` -- Modify: `package.json` - -**Interfaces:** -- Consumes: `checkImages({baseUrl, apiKey, signal, requestTimeoutMs})` from #38. -- Produces: `waitForContainer(inspect, {signal?, startupTimeoutMs?, pollIntervalMs?}) -> Promise`; `checkDockerHttp({baseUrl, apiKey, signal?, requestTimeoutMs?}) -> Promise`; `pnpm test:e2e:docker `. -- `inspect(signal)` returns Docker inspect's `State` and `NetworkSettings`; the CLI uses bounded `docker inspect`, while tests supply external-daemon state without requiring Docker. - -- [x] Write behavior tests: starting→healthy returns loopback port; exited, unhealthy, missing HEALTHCHECK, invalid publication fail; startup stalls/cancellation fail. Real HTTP tests require health200, unauthenticated401 and both fixture predictions, reject incorrect status/class, and abort stalled bodies. -- [x] Run `pnpm exec vitest run apps/server/test/e2e-docker.test.ts`: expected FAIL because verifier does not exist. -- [x] Implement checks and command. Share a120-second startup deadline with Docker inspect; each Docker command and HTTP request has a60-second bound. Use #38's signal handling and real fixture validator. -- [x] Re-run targeted tests: expected PASS; run full suite: expected all existing and new tests pass. -- [x] Commit verifier, command, tests and plan. - -### Task 2: Native Docker matrix and end-to-end verification - -**Files:** -- Modify: `.github/workflows/ci.yml`, `README.md` - -**Interfaces:** -- Consumes: Task1's `pnpm test:e2e:docker ` and test-key environment variable. -- Produces: independent `docker-e2e` matrix checks without registry credentials or image push. - -- [x] Add native runner/platform matrix, dependency setup without host build, Buildx build with `load: true`, `push: false`, and architecture-scoped cache. -- [x] Create/start named container with ephemeral loopback publication, readonly config, test API key; run verifier. -- [x] Add failure-only logs/state and always-run forced removal. Document local invocation and lifecycle. -- [x] Run native amd64 Docker build/start/check locally; expected correct predictions and cleanup. -- [x] Inject missing model, disabled HEALTHCHECK and failed inference/auth scenarios; expected nonzero result, useful diagnostics, removal. Unit tests cover HTTP stalls/readiness deadline. -- [x] Run full tests, built-server E2E, typecheck, lint, diff check: expected PASS. -- [x] Independent adversarial review; verify findings, fix substantive issues, repeat until no substantive findings (user instruction). -- [x] Commit, push feature branch, create PR closing#39; verify hosted amd64 and arm64 Docker checks plus existing jobs. - -**Hosted verification:** [PR#41](https://github.com/misskey-dev/sensitive-detector/pull/41)の[CI run42183772760](https://github.com/misskey-dev/sensitive-detector/actions/runs/42183772760)で全5ジョブが成功。amd64・arm64ともビルド、起動、HEALTHCHECK・認証・画像推論、コンテナ削除が成功した。ローカル全165テスト、型チェック、lint、既存server E2E、actionlintも成功。独立レビューで指摘されたDockerコマンドの時間制限とローカル手順の既存コンテナ保護を修正し、再レビューは実質的な指摘なし。 diff --git a/docs/superpowers/plans/2026-11-06-server-e2e.md b/docs/superpowers/plans/2026-11-06-server-e2e.md deleted file mode 100644 index a5c1fc7..0000000 --- a/docs/superpowers/plans/2026-11-06-server-e2e.md +++ /dev/null @@ -1,49 +0,0 @@ -# Server E2E CI Implementation Plan - -> Use superpowers:executing-plans to implement the tasks in order. - -**Goal:** Run fixed real-image regression checks against the built server in CI. - -**Architecture:** A shared TypeScript HTTP checker owns the fixtures and prediction assertions. A separate runner starts the built server with temporary configuration, waits for health, invokes the checker, and always stops its child. Existing Vitest tests cover checker failures; actual E2E and deliberate failure runs verify the process lifecycle. - -**Tech Stack:** Node.js 22+, tsx, Vitest, GitHub Actions. - -**Spec:** https://github.com/misskey-dev/sensitive-detector/issues/38 - -## Global Constraints - -- Commit redistributable 299×299 PNG fixtures with source, license, and expected class. -- Five unique classes, finite probabilities in [0, 1], expected class strictly highest; no exact probabilities. -- Missing images/model fail; startup limit 120 seconds, HTTP limit 60 seconds. -- Always terminate the server, including errors and signals. -- Reuse the HTTP checker from Docker issue #39. No production API or release workflow changes. - -## Review Focus - -- Health connection attempts and body reads must remain bounded by the startup deadline. -- Failed inference and malformed responses must fail even when HTTP status is 200. -- Child exit, spawn errors, cancellation, and assertion failures must release the process and temporary files. -- Fixture licenses and expected classes must have independent provenance. -- E2E must not resolve the application entrypoint to source or skip missing inputs. - -### Task 1: Shared real-image checker and fixtures - -**Files:** apps/server/e2e/check-images.ts, apps/server/e2e/fixtures/*, apps/server/test/e2e-check.test.ts - -**Interfaces:** Produces FIXTURES and checkImages({ baseUrl, apiKey, signal?, fixtures?, requestTimeoutMs? }). - -- [x] Write response-validation and HTTP failure tests; run and observe RED. -- [x] Add generated fixtures with prompts/license documentation, implement checker; observe GREEN. -- [x] Confirm real model classifies a generated cat as Neutral and a generated fictional-adult swimwear illustration as Sexy. - -### Task 2: Built-server runner and CI - -**Files:** apps/server/e2e/server.ts, apps/server/e2e/run.ts, package.json, .github/workflows/ci.yml, README.md - -**Interfaces:** Consumes Task 1 checker and fixtures; produces pnpm test:e2e. - -- [x] Run test:e2e before implementation and observe absent command. -- [x] Implement child lifecycle, temporary config, readiness, bounded requests and cleanup. -- [x] Add dedicated CI job and usage documentation. -- [x] Run E2E, intentional wrong expectation, missing image/model, readiness/request timeouts, cancellation, and process cleanup checks. -- [x] Run unit/integration tests, typecheck and lint; request whole-branch review and address findings. diff --git a/docs/superpowers/plans/2026-11-06-verified-docker-release.md b/docs/superpowers/plans/2026-11-06-verified-docker-release.md deleted file mode 100644 index d94419b..0000000 --- a/docs/superpowers/plans/2026-11-06-verified-docker-release.md +++ /dev/null @@ -1,38 +0,0 @@ -# Verified Docker Release Implementation Plan - -> **For agentic workers:** Use superpowers:executing-plans. The user requested implementing release-time verification using the existing Docker checks. - -**Goal:** リリース時も実際に検証したイメージだけを公開する。 -**Architecture:** native amd64/arm64でbuild/loadし、CIと共通のcomposite actionで検証する。actionが検証した不変のimage IDを出力し、そのIDをtag/pushする。再ビルドしない。両architectureの成功後、公開先のdigestから既存のsemver manifestを作る。 -**Spec:** この会話の「リリースworkflowでも同じ検証を通してからpush」。#41はマージ済み。 -**Decision:** キャッシュを使う再ビルドより、検証したimage IDの直接pushを選ぶ。同一成果物であることが明確。OCI転送ツールの追加は避け、既存Docker CLIを使う。受け渡し用に`build---linux-`補助タグがGHCRに残る。ユーザー向けsemverタグの規則は維持する。 - -## Constraints - -- amd64=`ubuntu-24.04`、arm64=`ubuntu-24.04-arm`。configだけをreadonly mountし、モデルはimage同梱。 -- HEALTHCHECK、health200、unauthenticated401、固定画像Neutral/Sexyの推論を必須とする。 -- 起動120秒、HTTP60秒、Docker管理操作60秒。pushは大きなlayer転送を考慮して最大600秒。 -- 検証失敗時はlogin/push/digest exportを実行しない。ログ・状態を出力し、コンテナ削除を試行する。 -- 本番レジストリへの実pushやrelease作成は、この実装作業では行わない。 -- 公開API・モデル判定・fixtureは変更しない。 - -## Tasks - -- [x] Baseline165testsを確認する。 -- [x] `.github/actions/test-docker-image/action.yml`に既存のDocker lifecycle/host setupを移し、immutable image IDで起動・出力する。CIはこのactionを使う。 -- [x] releaseworkflowをnativebuild/load→共通検証→login→同じimage IDのpush→registry digest exportに変更する。既存mergeは両方の成功を待つ。 -- [x] 実際のaction/stepのshellを使い、localregistryでtest/push/manifestを検証し、registry manifestのconfig digestが検証済みimage IDに一致することを確認する。 -- [x] 検証失敗時のpush阻止とcleanup、異常digest拒否をfault injectionで確認する。actionlint・全テスト・型・lint・serverE2Eを実行する。 -- [x] 独立エージェントの敵対的レビューで実質的な指摘がなくなるまで修正・再レビューする。 - -**PR verification:** 共通actionを使うamd64/arm64を含むCI全5ジョブの最終結果は、PR本文とChecksに記録する。 - -## Review Focus - -- testとpushの間で再ビルドや可変tagの再解決をしない。 -- composite内部のfailureがcleanup成功によって握りつぶされない。 -- release tagをcheckoutし、誤ったbranchのコードを公開しない。 -- digestがローカルimage IDではなく公開先のmanifest digestであること。 -- 片方が失敗した場合、semver manifestの公開を行わない。 - -**Review result:** checkoutの未修飾タグ名が同名branchを選び得る問題を公式helperで再現し、`github.sha`へ固定して解消した。再レビューで残る実質的な指摘なし。CodeRabbitも共通actionを含む全5ファイルをレビューし指摘なし。