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/healthliveness와 tested archive handoff가 있으나main도 publication하고 CI/runtime install이 unlocked이며 image ID/checksum/canary/non-root/same-digest 검증이 약하다.trex-mega-swing-v2: strictvX.Y.Ztag-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.0hosted run이 성공했다.
후속 hardening 이슈도 같은 방향을 가리킨다.
trex-core#57: https://github.com/fourblocks2020/trex-core/issues/57kis-data-hub#30: https://github.com/fourblocks2020/kis-data-hub/issues/30
두 이슈는 현재 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는 다음을 동시에 만족해야 한다.
- publication authority가 명시적인 requester-selected version에만 귀속될 것
- repository verification과 runtime dependency가 동일한 lock resolution을 사용할 것
- verify job에서 시험한 exact image만 publish job으로 전달되고 rebuild되지 않을 것
- source commit, image ID, archive checksum, registry digest가 연결될 것
- secret·운영 설정이 build context, layer, history, archive, metadata에 포함되지 않을 것
- installed image에 API, CLI 및 모든 Alembic migration payload가 존재할 것
- HTTP liveness와 PostgreSQL readiness/migration 검증을 구분할 것
- 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를 반환한다.pfhubCLI에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.tomlportfolio-hub/uv.lockportfolio-hub/app/main.pyportfolio-hub/app/api/health.pyportfolio-hub/app/db/migrations.pyportfolio-hub/app/cli/commands/health.pyportfolio-hub/alembic.iniportfolio-hub/migrations/versions/trex-core/.github/workflows/publish-image.ymltrex-core/Dockerfilekis-data-hub/.github/workflows/publish-image.ymlkis-data-hub/Dockerfilekis-data-hub/docs/container-operations.mdtrex-mega-swing-v2/.github/workflows/publish-image.ymltrex-mega-swing-v2/Dockerfiletrex-mega-swing-v2/scripts/image_release.pytrex-mega-swing-v2/tests/release/trex-mega-swing-v2/docs/container-operations.md- 성공한
trex-mega-swing-v2 v0.1.0publication run: https://github.com/fourblocks2020/trex-mega-swing-v2/actions/runs/31468042611
구현·검증 증거
- 구현 브랜치:
feat/25-ghcr-image-publication - 구현 기준:
origin/main35e85981af845489236c18d709d0545c830e7f03 - 로컬 구현 커밋:
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; 전체 pytest621 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.ZGit 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 후보
- Installed payload smoke
- networkless/read-only/non-root container에서
python -I -c 'import app',pfhub --help, uvicorn entrypoint 확인
- networkless/read-only/non-root container에서
- DB-disabled HTTP liveness
- 외부 dependency 없이 API를 기동하고
/health의status=ok,service=portfolio-hub,db_enabled=false확인
- 외부 dependency 없이 API를 기동하고
- Migration payload exact inventory
- source의 canonical migration 목록과 wheel/image 안의 목록이 non-empty이고 정확히 같은지 확인
alembic.ini및 resource lookup 계약 확인
- Isolated PostgreSQL migration/readiness smoke
- internal network의 synthetic PostgreSQL과 synthetic encryption key만 사용
- migration을 head까지 적용하고 Alembic revision 확인
- DB-enabled API 기동 후
/healthliveness와pfhub health-checkDB 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 순서 (확정)
다음 운영 순서를 계약으로 고정할 것인가?
- 별도 pre-start
pfhub migrate-db - 성공 후 application container start
/healthliveness와 별도 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-hubserver 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와 activeac-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 승인 없이는 수행하지 않는다.