Files
trex-core/.proofline/lines/line-0004/dcy-0004.md
T

80 lines
11 KiB
Markdown

---
id: "dcy-0004"
status: confirmed
---
# requester-versioned·runtime-locked image publication
## Problem
현재 `.github/workflows/publish-image.yml`은 `main`과 `v*` push 모두에서 GHCR publication을 실행한다. 이 때문에 문서·governance-only `main` commit도 새 registry artifact를 만들며, publication을 요청자가 선택한 release version과 분리해서 통제할 수 없다.
repository verification은 `uv sync --extra dev --locked`를 사용하지만 현재 `Dockerfile`은 `pyproject.toml`만 복사한 뒤 `pip install .`을 실행한다. 따라서 CI 검증 환경과 built image의 runtime dependency graph가 동일한 repository-owned `uv.lock` resolution을 사용한다는 보장이 없다.
이 Discovery는 기존의 single build, built-image smoke, image ID, archive SHA-256, canary scan, OCI source revision, rebuild 없는 publish와 multi-tag same-digest 계약을 유지하면서 publication authority를 strict requester-selected version tag로 제한하고 runtime dependency identity를 lockfile에 결속한다.
## Evidence
- GitHub issue #57 `https://github.com/fourblocks2020/trex-core/issues/57`은 requester-versioned·runtime-locked publication 변경과 완료 조건을 기록한 원본 제안이다.
- 요청자는 모든 OQ에서 정확히 하나의 선택지를 선택한 뒤 이슈 본문 갱신과 Discovery 승인을 명시적으로 지시했다.
- 승인 결정에 따라 `vX.Y.Z` Git tag는 container publication version authority이며 `pyproject.toml`의 `[project].version`과 독립적이다. 두 값을 일치시키는 검증이나 package version 변경은 요구하지 않는다.
- 승인 결정에 따라 별도 비게시 CI를 추가하지 않는다. full Ruff와 full pytest는 strict version-tag publication gate에서 실행하고, 일반 `main`/branch push에는 이 Discovery가 별도 CI workflow를 추가하지 않는다.
- 조사 기준 source revision은 `1a2b50072238ea72186d99b3ec951fc9511100aa`이며 조사 시 `main`과 `origin/main`은 동기화되어 있고 worktree는 clean이었다.
- `.github/workflows/publish-image.yml`은 현재 `main`과 `v*` push를 모두 trigger로 사용하고, exact `github.sha` checkout 후 `uv sync --extra dev --locked`, full Ruff, full pytest, single Docker build, built-image smoke, image ID 기록, archive export·canary scan·SHA-256 기록과 immutable artifact handoff를 수행한다. 별도 publish job만 `packages: write`를 가지며 tested artifact를 재빌드 없이 게시한다.
- `Dockerfile`은 현재 `pyproject.toml`과 `trex_core/`만 copy한 뒤 `pip install --no-cache-dir .`을 실행하므로 `uv.lock`을 runtime dependency installation input으로 사용하지 않는다.
- `scripts/image_release.py::release_tags()`는 현재 `refs/heads/main`을 허용하고 `v`로 시작하는 넓은 tag 형식을 허용한다. publish helper는 archive checksum, tested/loaded image ID, OCI revision, registry credential 경계, 두 tag의 same-digest와 성공 evidence 생성을 검증하지만 strict `vX.Y.Z` event만 허용하지는 않는다.
- `tests/release/test_image_contracts.py`는 현재 `main`/`v*` trigger와 Dockerfile의 기존 COPY allowlist를 고정하고 있어 승인된 trigger·runtime lock 계약에 맞춰 변경해야 한다.
- `docs/operations.md`는 source-derived tag를 stable selector로, registry digest를 immutable deployment/rollback identity로 설명하고 runtime config·secret 외부 주입 및 reset 분리 경계를 보존한다. version tag 생성·push가 별도 사용자 승인 외부 쓰기라는 절차는 아직 설명하지 않는다.
- 기존 active AC 중 AC-0004는 `main` 또는 `v*` event의 verification-gated publication, AC-0006은 source tag·release tag·digest 추적성, AC-0008은 Docker COPY allowlist와 secret/config 비포함을 소유한다. AC-0005의 tested-artifact identity와 AC-0007의 built-container smoke는 이번 변경에서도 의미를 유지한다.
- 비교 기준 `trex-mega-swing-v2` revision `88e90caea7e4ab740ed785fc1f57aed8b6f2da5f`의 workflow는 strict `vX.Y.Z` tag-only trigger, event/ref/commit fail-closed validation, exact tagged commit checkout, locked verify, single build와 tested-artifact publication을 사용한다. workflow run `31468042611`은 `v0.1.0` tag에서 성공했다.
- 기준 revision에서 `proofline validate`는 exit `0`, `uv run ruff check .`은 `All checks passed!`, `uv run pytest -q`는 `1040 passed, 51 skipped`를 반환했다. skip은 환경 의존 integration 경계이며 현재 repository baseline이 승인된 변경을 이미 구현했다는 의미는 아니다.
## Scope
- AC admission은 기존 동작의 권한·식별자·build input을 변경하는 `update`와 새로운 runtime lock 동작을 정의하는 `create`다.
- `update`: AC-0004의 publication event를 일반 `main`/`v*`에서 strict version-tag-only로 변경하고 malformed/non-version event가 registry credential·write 전에 실패하도록 한다.
- `update`: AC-0006에 strict requester-selected `vX.Y.Z` version tag, source-derived tag와 version tag의 same-digest, version을 포함한 publication evidence를 반영한다.
- `update`: AC-0008의 좁은 Docker build input allowlist에 repository-owned `uv.lock`과 locked installation에 필요한 도구만 추가하면서 기존 config·credential·canary 비포함 경계를 보존한다.
- `create`: repository verification과 runtime image가 동일한 repository-owned lock resolution을 사용하고 lock mismatch 또는 unlocked runtime install을 거부하는 독립적인 장기 AC를 추가한다.
- AC-0005의 tested-artifact identity와 AC-0007의 built-container package·entrypoint smoke는 변경 없이 유지한다.
- GHCR publication workflow의 유일한 trigger는 requester가 exact source commit에 생성·push한 strict `vX.Y.Z` tag다. 각 숫자 구성요소는 `0` 또는 leading zero가 없는 non-negative decimal integer여야 하며 prerelease/build suffix, malformed tag, branch ref와 non-push event는 허용하지 않는다.
- workflow는 event·ref·40자리 lowercase source commit을 registry credential 사용 전에 fail-closed로 검증하고, exact tagged commit을 checkout했음을 다시 확인한다.
- version tag는 container publication identity이며 `pyproject.toml`의 `[project].version`과 독립적으로 취급한다. package version 일치 검사, 자동 version 증분과 package metadata 변경을 수행하지 않는다.
- 일반 `main`과 branch push는 registry login, `docker push`, `packages: write` 또는 image publication을 수행하지 않는다. 요청자 결정에 따라 이번 Scope는 일반 `main`/branch push를 위한 별도 비게시 CI workflow를 추가하지 않는다.
- 허용된 version-tag event는 exact source checkout 후 pinned `uv`와 repository-owned `uv.lock`으로 full Ruff와 full pytest를 실행하고, 성공한 경우에만 이미지를 한 번 빌드한다.
- Docker build는 `pyproject.toml`, `uv.lock`과 allowlisted package source로 runtime environment를 `uv sync --locked --no-dev --no-editable` 또는 의미상 동등한 locked 방식으로 설치한다. lock mismatch, missing lockfile 또는 lock을 우회한 runtime dependency install은 build/publication 전에 실패한다.
- 동일한 tested image에 `:<source-commit-first-12>`와 `:vX.Y.Z`를 부여하고 두 tag가 동일한 registry digest를 가리키는지 확인한다. immutable deployment·rollback identity는 `@sha256:<digest>`다.
- 기존 `scripts/image_release.py`의 archive checksum, tested/loaded image ID, OCI revision, canary surface scan, no-rebuild publication, registry credential 경계와 multi-tag same-digest 검증을 유지한다.
- `latest`를 생성하지 않으며 성공 evidence에는 source revision, immutable image reference, digest와 selected version tag를 함께 기록한다.
- workflow contract와 helper tests는 일반 branch/main non-publication, strict event validation, exact checkout, lock mismatch, locked runtime build, same-digest tags, no-`latest`, no-rebuild 및 기존 failure boundaries를 검증한다.
- operator documentation은 version 선택과 Git tag 생성·push가 구현과 분리된 사용자 승인 외부 쓰기이고, tag push가 verify/publication을 시작한다는 점을 설명한다.
## Out of Scope
- 이 Discovery 또는 후속 구현 중 concrete version 선택, version tag 생성·push, 최초·후속 GHCR package write, production deployment 또는 운영 서버 pull/restart
- 일반 `main`/branch push를 위한 별도 lint/test CI workflow 신설 또는 기존 publication workflow 외 CI 정책 변경
- `pyproject.toml` package version 변경, version tag와 package version의 일치 강제 또는 자동 version 증분
- 기존 published tag·digest 삭제, 이동 또는 registry retention 변경
- live credential, 실제 account·database·RabbitMQ·Redis·KIS·portfolio-hub 연결, database migration/reset 또는 운영 데이터 변경
- image signing, SBOM, provenance attestation, multi-architecture build와 moving `latest`
- 다른 TREX repository의 workflow, image 또는 release policy 변경
## Risks and Unknowns
### Risks
- trigger 변경 후 일반 `main` push는 새 runtime image뿐 아니라 현재 publication workflow가 제공하던 full Ruff/pytest도 실행하지 않는다. 이는 요청자가 선택한 정책이며, branch protection 또는 지속적 검증이 필요해지면 registry 권한이 없는 별도 CI 변경으로 다룬다.
- version tag와 package version을 독립적으로 두면 image `:vX.Y.Z`와 installed Python package metadata version이 다를 수 있다. container publication version authority는 Git tag임을 문서화하고, package version synchronization이 필요해지면 별도 변경으로 다룬다.
- GitHub trigger glob만으로는 malformed `v*.*.*`를 모두 배제할 수 없다. workflow 내부 helper가 strict ref/event/commit을 registry credential 전에 재검증해야 한다.
- Docker build가 lockfile을 copy만 하고 실제 runtime install에 소비하지 않으면 CI lock PASS만으로 runtime reproducibility를 주장할 수 없다. build contract와 tests가 locked runtime command 및 mismatch failure를 직접 검증한다.
- version tag와 source-derived tag를 별도 build에서 만들면 같은 release identity가 서로 다른 image를 가리킬 수 있다. 단일 tested artifact를 재빌드 없이 두 tag로 push하고 registry digest equality를 확인한다.
- 기존 commit-derived selector와 immutable digest는 보존해야 하며 migration 과정에서 과거 tag를 삭제하거나 이동하지 않는다.
- tag 생성·push는 즉시 publication workflow와 registry write를 유발할 수 있다. operator 문서가 이를 별도 사용자 승인 외부 쓰기로 명시하고 구현·테스트 중 실제 tag push를 금지한다.
### Approval State
- Discovery status: `confirmed`
- 승인 근거: 요청자가 이슈의 모든 OQ에서 하나의 선택지를 명시적으로 선택한 뒤 이슈 본문 업데이트와 승인을 지시했다.
- 채택 결정: version tag와 package version은 독립적으로 취급하며, 일반 `main`/branch push용 별도 비게시 CI는 추가하지 않는다.
- 추가로 필요한 제품 결정: 없음
- 이 승인은 Discovery 범위 확인이며 Requirement/AC 승인, 구현, tag 생성·push, GHCR publication, delivery, release 또는 deployment 완료를 의미하지 않는다.