Files

18 KiB

id, status
id status
dcy-0002 confirmed

Requester-versioned GHCR 이미지 게시 계약

Problem

배경

portfolio-hub도 다른 TREX 서비스처럼 검증된 OCI image를 GHCR에 게시하고, 운영자가 immutable digest로 배포·롤백할 수 있어야 한다.

비교 대상의 현재 상태는 서로 다르다.

  • trex-core: build-once, image ID, archive SHA-256, OCI revision, canary scan, no-rebuild publish, selector digest 검증은 강하지만 main과 v* push 모두 publication하며 runtime dependency 설치가 uv.lock에 묶이지 않는다.
  • kis-data-hub: built-container /health liveness와 tested archive handoff가 있으나 main도 publication하고 CI/runtime install이 unlocked이며 image ID/checksum/canary/non-root/same-digest 검증이 약하다.
  • trex-mega-swing-v2: strict vX.Y.Z tag-only, locked CI/runtime, build-once, installed/startup smoke, image ID/checksum/revision/canary, no-rebuild publish, commit/version selector same-digest, non-root, digest deploy/rollback 경계를 구현했고 실제 v0.1.0 hosted run이 성공했다.

후속 hardening 이슈도 같은 방향을 가리킨다.

두 이슈는 현재 OPEN proposal이며, 기존 방식을 공통 표준으로 유지하는 근거가 아니라 trex-mega-swing-v2형 requester-versioned publication으로 이관하려는 근거다.

문제 정의

일반 main push마다 image를 게시하면 문서·governance-only 변경도 새 registry artifact를 만들며 requester의 release/version 선택과 publication이 분리되지 않는다. 반대로 image build만 성공했다고 운영 가능한 artifact가 되는 것도 아니다.

portfolio-hub는 다음을 동시에 만족해야 한다.

  1. publication authority가 명시적인 requester-selected version에만 귀속될 것
  2. repository verification과 runtime dependency가 동일한 lock resolution을 사용할 것
  3. verify job에서 시험한 exact image만 publish job으로 전달되고 rebuild되지 않을 것
  4. source commit, image ID, archive checksum, registry digest가 연결될 것
  5. secret·운영 설정이 build context, layer, history, archive, metadata에 포함되지 않을 것
  6. installed image에 API, CLI 및 모든 Alembic migration payload가 존재할 것
  7. HTTP liveness와 PostgreSQL readiness/migration 검증을 구분할 것
  8. image rollback과 DB migration rollback을 구분할 것

Evidence

원문·승인·사후 문서화 경계

  • 원문: https://github.com/fourblocks2020/portfolio-hub/issues/25
  • 원문 제목: discovery: requester-versioned GHCR 이미지 게시 계약
  • 원문 상태: OPEN
  • Discovery 승인 댓글: https://github.com/fourblocks2020/portfolio-hub/issues/25#issuecomment-5277036560
  • 승인 시각(KST): 2026-08-13T15:51:34+09:00
  • 승인 선택: OQ-1-A, OQ-2-B, OQ-3-A, OQ-4-A, OQ-5-A, OQ-6-A
  • 요청자는 2026-08-13 이슈 #25 원문을 최대한 포함한 승인 상태의 Discovery와 승인된 REQ/AC를 새 Line에 사후 작성하도록 명시했다.
  • 이 Line은 이미 수행된 로컬 구현을 승인된 지속 사양으로 포착한다. #25 구현이 ProofLine 정식 시퀀스로 수행됐다는 주장, ProofLine delivery closure, push, publication, release 또는 deployment 완료를 소급 생성하지 않는다.

이슈 원문의 문서 성격

  • 유형: Discovery 제안
  • 상태: approved / requester confirmed
  • 승인 시각(KST): 2026-08-13T15:51:34+09:00
  • 승인 결정: OQ-1-A, OQ-2-B, OQ-3-A, OQ-4-A, OQ-5-A, OQ-6-A
  • 대상 저장소: fourblocks2020/portfolio-hub
  • 목적: portfolio-hub의 GHCR 운영 이미지 게시 방식을 결정하기 위한 문제·근거·경계·미결정 질문 정리

이 Discovery는 requester가 아래 OQ 선택으로 승인했다. 이 승인은 portfolio-hub 저장소 안의 후속 요구사항 작성과 bounded implementation 범위 제안을 허용하지만, PR 생성, Git tag 생성·push, GHCR publication, GitHub Release 생성, 운영 배포·재시작 또는 production DB migration 실행을 승인하지 않는다. 각 외부 write는 별도 requester 지시가 필요하다.

관찰된 portfolio-hub 현황

현재 main 기준:

  • Python >=3.11, FastAPI/uvicorn HTTP service다.
  • uv.lock이 존재하며 uv lock --check가 통과한다.
  • /health는 HTTP 200 응답과 status, service, version, checked_at, db_enabled를 반환한다.
  • pfhub CLI에 init-db, migrate-db, health-check가 있다.
  • PostgreSQL과 Alembic을 사용하며 migrations/versions에 28개 migration이 있다.
  • DB-enabled startup은 CREDENTIAL_ENCRYPTION_KEY를 요구하며, 설정에 따라 initial admin user를 생성할 수 있다.
  • Dockerfile, deny-by-default .dockerignore, image publication workflow, release helper/tests, container operations 문서는 아직 없다.

패키징 위험

현재 wheel을 실제 build해 inventory를 확인한 결과:

wheel: portfoliohub-0.1.0-py3-none-any.whl
members: 61
migration_members: 0
alembic_ini: False
pfhub_entrypoint: True

현재 migration loader는 설치된 app 위치의 상위에서 alembic.ini와 migrations/를 찾는다. 따라서 .venv만 runtime stage로 옮기는 단순 multi-stage image는 API import와 pfhub --help는 통과하면서도 운영의 pfhub migrate-db가 실패할 수 있다.

이 문제는 첫 image publication 전에 fail-closed package/runtime contract로 해결해야 한다.

이슈 원문 근거

  • portfolio-hub/pyproject.toml
  • portfolio-hub/uv.lock
  • portfolio-hub/app/main.py
  • portfolio-hub/app/api/health.py
  • portfolio-hub/app/db/migrations.py
  • portfolio-hub/app/cli/commands/health.py
  • portfolio-hub/alembic.ini
  • portfolio-hub/migrations/versions/
  • trex-core/.github/workflows/publish-image.yml
  • trex-core/Dockerfile
  • kis-data-hub/.github/workflows/publish-image.yml
  • kis-data-hub/Dockerfile
  • kis-data-hub/docs/container-operations.md
  • trex-mega-swing-v2/.github/workflows/publish-image.yml
  • trex-mega-swing-v2/Dockerfile
  • trex-mega-swing-v2/scripts/image_release.py
  • trex-mega-swing-v2/tests/release/
  • trex-mega-swing-v2/docs/container-operations.md
  • 성공한 trex-mega-swing-v2 v0.1.0 publication run: https://github.com/fourblocks2020/trex-mega-swing-v2/actions/runs/31468042611

구현·검증 증거

  • 구현 브랜치: feat/25-ghcr-image-publication
  • 구현 기준: origin/main 35e85981af845489236c18d709d0545c830e7f03
  • 로컬 구현 커밋: 96260b6302e050c6e564f3eca4e723558f26246f
  • 구현 진행 댓글: https://github.com/fourblocks2020/portfolio-hub/issues/25#issuecomment-5277080186
  • 구현 완료 댓글: https://github.com/fourblocks2020/portfolio-hub/issues/25#issuecomment-5277346458
  • 최종 증거: release tests 23 passed; 전체 pytest 621 passed, 6 skipped; uv lock --check, scoped Ruff, git diff --check, 실제 Python 3.11 image build/canary scan, installed-image/PostgreSQL startup smoke 통과.
  • 독립 최초 리뷰의 MAJOR 1건과 MINOR 1건은 수정·회귀검증됐고 독립 재리뷰 PASS에서 모두 CLOSED됐다.
  • 두 댓글의 exact body는 이 Line의 evidence/implementation-progress.md와 evidence/implementation-complete.md에 비정식 증적으로 보존한다.

Scope

승인된 방향

publication·공급망 계약은 trex-mega-swing-v2를 기준으로 채택하고, smoke와 payload 검증은 portfolio-hub에 맞춘다.

공통 publication 계약 후보

  • strict vX.Y.Z Git tag push만 publication event로 허용
  • main, 일반 branch, PR, workflow_dispatch, malformed/prerelease tag에서 registry write 금지
  • 자동 version 증가 및 moving latest 금지
  • exact tagged commit checkout 및 checked-out HEAD 일치 검증
  • pinned uv + uv sync --extra dev --locked 검증
  • runtime도 repository-owned uv.lock으로 --locked --no-dev --no-editable 설치
  • full pytest와 새 release helper/contract test에 대한 정직한 lint gate
  • source image를 한 번만 build
  • OCI full revision, tested image ID, exported archive SHA-256 기록
  • synthetic canary를 image filesystem, history, inspect metadata, archive 및 helper output에서 검사
  • publish job은 checksum/image ID/revision을 확인한 동일 archive를 load하고 rebuild 없이 게시
  • 다음 두 selector만 부여하고 동일 registry digest를 검증
    • ghcr.io/fourblocks2020/portfolio-hub:<source-commit-first-12>
    • ghcr.io/fourblocks2020/portfolio-hub:vX.Y.Z
  • 실제 deploy/rollback identity는 ghcr.io/fourblocks2020/portfolio-hub@sha256:<digest>
  • 기본 runtime user는 non-root 고정 UID/GID, 운영 root filesystem은 read-only, 필요한 경로만 명시적으로 writable mount

portfolio-hub 전용 verification 후보

  1. Installed payload smoke
    • networkless/read-only/non-root container에서 python -I -c 'import app', pfhub --help, uvicorn entrypoint 확인
  2. DB-disabled HTTP liveness
    • 외부 dependency 없이 API를 기동하고 /health의 status=ok, service=portfolio-hub, db_enabled=false 확인
  3. Migration payload exact inventory
    • source의 canonical migration 목록과 wheel/image 안의 목록이 non-empty이고 정확히 같은지 확인
    • alembic.ini 및 resource lookup 계약 확인
  4. Isolated PostgreSQL migration/readiness smoke
    • internal network의 synthetic PostgreSQL과 synthetic encryption key만 사용
    • migration을 head까지 적용하고 Alembic revision 확인
    • DB-enabled API 기동 후 /health liveness와 pfhub health-check DB ping을 별도로 확인
    • production credential/DB 접속 금지, bootstrap-user side effect 억제 또는 격리 DB로 제한
    • container/network/volume cleanup까지 PASS 조건에 포함

원문 범위 후보 — In scope

In scope

  • Dockerfile 및 deny-by-default .dockerignore
  • strict tag-only GHCR publication workflow
  • image release helper와 contract/regression tests
  • locked verification/runtime dependency 설치
  • installed payload·HTTP·PostgreSQL/Alembic smoke
  • migration resource/package layout의 최소 수정
  • immutable digest 중심의 container operations 문서
  • secret, non-root, read-only filesystem, writable mount, migration/rollback 경계

확정 결정(OQ) — 원문 전체

승인 기록: requester가 각 OQ에서 하나씩 선택했으며, 아래 [x] 항목이 승인된 scope authority다. 미선택 항목은 구현 범위가 아니다.

OQ-1 — migration payload 위치 (확정)

어떤 방식을 canonical runtime contract로 채택할 것인가?

  • OQ-1-A (승인): migration과 Alembic 설정을 installable package data로 포함하고 importlib.resources 기반으로 조회
  • OQ-1-B: runtime image의 고정 경로에 alembic.ini와 migrations/를 명시적으로 복사하고 loader가 그 경로를 사용

결정 근거: A는 wheel/image payload parity를 검증하기 쉽고 source checkout 경로에 덜 의존한다. 단, Alembic script layout과 CLI 호환성을 prototype으로 검증해야 한다.

OQ-2 — publication smoke의 DB 깊이 (확정)

  • OQ-2-A: DB-disabled /health만 publication gate로 두고 PostgreSQL migration은 별도 CI에서 검증
  • OQ-2-B (승인): publication gate 안에서 isolated PostgreSQL migration + DB ping까지 필수로 검증

결정 근거: B. 이 서비스의 운영 가능성은 migration payload와 DB 연결에 의존하며 /health는 DB ping을 수행하지 않는다.

OQ-3 — migration과 application startup 순서 (확정)

다음 운영 순서를 계약으로 고정할 것인가?

  1. 별도 pre-start pfhub migrate-db
  2. 성공 후 application container start
  3. /health liveness와 별도 DB readiness 확인
  • OQ-3-A (승인): 위 pre-start migration 순서를 운영 계약으로 고정
  • OQ-3-B: application startup 과정에서 migration을 수행

결정 근거: A는 application startup이 production schema mutation을 암묵적으로 수행하지 않게 한다.

OQ-4 — runtime entrypoint (확정)

  • OQ-4-A (승인): image 기본 command를 uvicorn app.main:app ...로 명시하고 shutdown/worker/root-path/forwarded-header 계약을 operations 문서와 smoke에 고정
  • OQ-4-B: 명시적인 portfolio-hub server CLI/entrypoint를 추가하고 이를 image 기본 command로 사용

결정 근거: 첫 범위에서는 A로 publication 구축 범위를 제한한다. 별도 server CLI는 독립 필요성이 확인될 때 추가한다.

OQ-5 — Python minor baseline (확정)

  • OQ-5-A (승인): 현재 저장소 계약과 일치하도록 image와 hosted verification을 Python 3.11로 고정
  • OQ-5-B: 이 범위에서 Python 3.12로 함께 승격

결정 근거: A. runtime 승격을 publication 구축에 묶지 않는다.

OQ-6 — branch CI와 publication 분리 (확정)

Tag-only publication과 별개로 일반 branch/main에서 어떤 검증을 수행할 것인가?

  • OQ-6-A (승인): branch/main에서 registry write 없이 full pytest와 release/container contract 검증을 수행하고, strict version tag workflow만 packages: write와 publication을 수행
  • OQ-6-B: branch/main에서는 기존 repository 검증만 수행하고 release/container contract와 publication 검증은 strict version tag workflow에서만 수행

결정 근거: A는 registry write 권한을 tag publication에만 제한하면서 publication 결함을 tag push 전에 발견할 수 있다.

승인 결정 요약

OQ 승인 선택 확정 내용
OQ-1 A migration/Alembic 설정을 installable package data로 포함하고 importlib.resources로 조회
OQ-2 B publication gate에서 isolated PostgreSQL migration과 DB ping을 필수 검증
OQ-3 A pfhub migrate-db 성공 후 application을 시작하는 pre-start migration 순서
OQ-4 A 첫 범위에서는 명시적인 uvicorn command 사용, 운영 파라미터를 문서·smoke에 고정
OQ-5 A Python 3.11 고정
OQ-6 A branch/main은 registry write 없이 full pytest와 release/container contract 검증, strict version tag만 publication

Discovery 결정 게이트

다음 결정 게이트는 requester의 OQ 선택 및 본 Discovery 승인으로 확정되었다.

  • OQ-1 migration payload/resource layout
  • OQ-2 PostgreSQL publication smoke 깊이
  • OQ-3 pre-start migration 운영 순서
  • OQ-4 runtime entrypoint
  • OQ-5 Python minor baseline
  • OQ-6 branch CI와 publication workflow 분리 방식
  • image name 및 selector 규칙
  • production writable mount 및 secret injection 경계
  • 구현이 tag push/publication/deployment를 자동 승인하지 않는다는 외부-write 경계

Discovery 성공 조건

다음 산출이 완료되면 요구사항/implementation scope를 제안할 수 있다.

  • 비교 대상 세 서비스의 현재 방식과 목표 방식 차이가 위 근거와 일치한다.
  • portfolio-hub의 migration payload 실패 seam을 재현 가능한 contract로 정의한다.
  • OQ-1~OQ-6이 requester 결정으로 해소된다.
  • HTTP liveness, DB readiness, schema migration, publication, deployment의 상태가 서로 분리된다.
  • secret·non-root·read-only·cleanup 및 immutable rollback 경계가 확정된다.
  • 후속 요구사항에서 구현 AC를 source/lock → build → smoke → artifact handoff → publish → operator handoff 전체에 추적한다.

Acceptance admission

기존 active AC는 포트폴리오 ledger 목록 집계 계약으로 이 publication capability와 의미가 다르므로 update 또는 satisfy 대상으로 사용하지 않는다. 이 Line은 다음 일곱 가지 version-independent behavior를 새 AC로 create한다.

  • installable Alembic migration resource와 exact inventory parity
  • Python 3.11 locked non-root image와 deny-by-default build context
  • branch 검증과 strict version-tag publication authority 분리
  • build-once tested-artifact identity·integrity·provenance
  • installed payload와 DB-disabled HTTP liveness
  • isolated PostgreSQL migration·readiness와 exact cleanup
  • immutable operator handoff와 deployment exclusion

Out of Scope

원문 범위 후보 — Out of scope

Out of scope

  • 이 Discovery에서 실제 version 결정 또는 tag push
  • GHCR image publication 및 package visibility 변경
  • GitHub Release 생성
  • 운영 서버/Dockge/Compose 배포 또는 재시작
  • production credential·DB 사용
  • production DB migration, downgrade, data repair, reset 또는 backup 실행
  • application API/domain behavior 변경
  • deployment orchestrator 선정·구축
  • unrelated repository-wide lint debt 정리
  • trex-core #57 또는 kis-data-hub #30 구현

사후 ProofLine 경계

  • 이 사후 사양 포착은 구현이 ProofLine 정식 시퀀스로 수행됐다는 주장이나 implementation/delivery/release/deployment lifecycle closure를 만들지 않는다.
  • 구현 진행·완료 댓글은 canonical lifecycle artifact가 아니라 출처가 명시된 비정식 evidence다.
  • 후속 승인 사양은 req-0002와 active ac-0006~ac-0012로 추적한다. 이는 원문의 미완료 성공 조건인 source/lock → build → smoke → artifact handoff → publish → operator handoff AC 추적을 사후 충족한다.
  • 기능 브랜치 push, PR, Git tag 생성·push, GHCR publication, GitHub Release, 운영 배포·재시작 및 production DB migration은 별도 requester 승인 없이는 수행하지 않는다.