diff --git a/.proofline/criteria/ac-0006.md b/.proofline/criteria/ac-0006.md new file mode 100644 index 0000000..168ea09 --- /dev/null +++ b/.proofline/criteria/ac-0006.md @@ -0,0 +1,16 @@ +--- +id: "ac-0006" +status: active +--- + +# Installable Alembic migration payload와 exact inventory parity + +## Criterion + +`portfolio-hub`의 Alembic 설정, script template 및 모든 migration version은 wheel과 runtime image에 installable package data로 포함되어야 하며 runtime loader는 source checkout의 상위 경로에 의존하지 않고 `importlib.resources`로 이를 조회해야 한다. source, wheel 및 image의 canonical migration inventory는 non-empty이고 정확히 같아야 하며 migration 수를 영구 상수로 고정하지 않아야 한다. + +## Verification + +- 성공 조건: source checkout과 installed wheel/image에서 migration 경로 조회와 `pfhub migrate-db`가 가능하고, 세 inventory가 non-empty exact match다. +- 실패·경계 조건: `alembic.ini`, script template 또는 migration 하나라도 누락·추가·이름 불일치하면 검증이 실패하며, 단순 package import 또는 `pfhub --help` 성공으로 migration payload를 대체하지 않는다. +- 검사 방법: package-data 설정과 `app/db/migrations.py`를 검사하고 source↔wheel/image inventory regression 및 isolated PostgreSQL migration smoke를 실행한다. diff --git a/.proofline/criteria/ac-0007.md b/.proofline/criteria/ac-0007.md new file mode 100644 index 0000000..dc32a84 --- /dev/null +++ b/.proofline/criteria/ac-0007.md @@ -0,0 +1,16 @@ +--- +id: "ac-0007" +status: active +--- + +# Locked Python 3.11 non-root image와 deny-by-default context + +## Criterion + +Repository-root image build는 Python 3.11을 사용하고 repository-owned `uv.lock`에 고정된 runtime dependency를 `--locked --no-dev --no-editable` 경계로 설치해야 한다. 최종 image는 고정 non-root UID/GID로 실행되고 `.dockerignore`와 명시적 `COPY` allowlist가 source control metadata, local environment, credential, 운영 설정 및 불필요한 파일을 build context/layer에서 제외해야 한다. + +## Verification + +- 성공 조건: hosted/local verification과 runtime image가 Python 3.11·동일 lock resolution을 사용하고 기본 user가 non-root이며 필요한 package/runtime 파일만 image에 존재한다. +- 실패·경계 조건: lock drift, broad `COPY .`, root default user, `.env`·key·token·local DB·VCS/worktree metadata의 context 포함은 계약 실패다. +- 검사 방법: `uv lock --check`, Dockerfile/`.dockerignore` contract tests, image inspect의 UID/GID 및 synthetic canary scan을 실행한다. diff --git a/.proofline/criteria/ac-0008.md b/.proofline/criteria/ac-0008.md new file mode 100644 index 0000000..bf59f6f --- /dev/null +++ b/.proofline/criteria/ac-0008.md @@ -0,0 +1,16 @@ +--- +id: "ac-0008" +status: active +--- + +# Branch 검증과 requester-versioned publication authority 분리 + +## Criterion + +일반 branch와 `main`은 registry write authority 없이 full pytest와 release/container contract를 실행해야 한다. GHCR publication은 exact `vX.Y.Z` Git tag push만 허용하고 malformed tag, prerelease tag, `workflow_dispatch`, branch/main/PR event에서는 registry login 또는 push를 수행하지 않아야 한다. Publication job만 최소 `packages: write` 권한을 가지며 automatic version 증가와 moving `latest`를 만들지 않아야 한다. + +## Verification + +- 성공 조건: branch/main verification workflow는 registry credential·write path 없이 회귀와 release contract를 검사하고 strict version-tag event만 publication path에 도달한다. +- 실패·경계 조건: tag shape 또는 checked-out HEAD가 expected tag commit과 다르면 login/push 전에 실패하며, 다른 event가 publication 권한이나 write step을 얻으면 실패다. +- 검사 방법: workflow event/permissions/step-order contract tests와 representative valid·invalid ref helper tests를 실행한다. diff --git a/.proofline/criteria/ac-0009.md b/.proofline/criteria/ac-0009.md new file mode 100644 index 0000000..16450e4 --- /dev/null +++ b/.proofline/criteria/ac-0009.md @@ -0,0 +1,16 @@ +--- +id: "ac-0009" +status: active +--- + +# Build-once tested-artifact identity·integrity·provenance + +## Criterion + +Verify 단계는 exact source에서 image를 한 번만 build하고 tested image ID, full OCI source revision 및 exported archive SHA-256을 기록해야 한다. Publish 단계는 archive checksum·loaded image ID·revision을 재검증한 뒤 rebuild 없이 동일 image에 source commit selector와 requester version selector만 부여·push하고 두 selector가 동일 registry digest를 가리키는지 확인해야 한다. + +## Verification + +- 성공 조건: build→smoke→export→artifact handoff→load→tag→push가 하나의 tested image identity를 보존하고 source revision, archive checksum, selectors 및 registry digest가 추적 가능하다. +- 실패·경계 조건: archive 손상, ID/revision 불일치, publish-side build, selector digest 불일치 또는 malformed/missing digest는 nonzero이며 성공 summary를 금지한다. Login/push 전 검증 실패는 registry write 전에 중단한다. +- 검사 방법: release helper unit/contract tests에서 command ordering과 no-rebuild/no-login-on-precheck-failure를 확인하고 실제 local archive scan/load identity를 검증한다. diff --git a/.proofline/criteria/ac-0010.md b/.proofline/criteria/ac-0010.md new file mode 100644 index 0000000..6016a2d --- /dev/null +++ b/.proofline/criteria/ac-0010.md @@ -0,0 +1,16 @@ +--- +id: "ac-0010" +status: active +--- + +# Installed payload와 DB-disabled HTTP liveness + +## Criterion + +검증된 image는 networkless·read-only·non-root 조건에서 `app` package import, `pfhub` CLI 및 uvicorn application entrypoint를 제공해야 한다. 외부 dependency와 DB 없이 API를 기동할 수 있고 `/health`는 HTTP 200과 `status=ok`, `service=portfolio-hub`, `db_enabled=false`의 정확한 shape를 반환해야 한다. + +## Verification + +- 성공 조건: installed image에서 public import/CLI/entrypoint가 실행되고 DB-disabled health payload가 계약과 일치한다. +- 실패·경계 조건: source checkout bind mount에 의존하거나 help-only 검사로 HTTP liveness를 대체하거나 health payload가 누락·오형이면 실패다. +- 검사 방법: built-image installed payload smoke와 DB-disabled startup/HTTP probe를 실행하며 기본 non-root/read-only 조건을 함께 확인한다. diff --git a/.proofline/criteria/ac-0011.md b/.proofline/criteria/ac-0011.md new file mode 100644 index 0000000..ca8a2cf --- /dev/null +++ b/.proofline/criteria/ac-0011.md @@ -0,0 +1,16 @@ +--- +id: "ac-0011" +status: active +--- + +# Isolated PostgreSQL migration·readiness와 exact cleanup + +## Criterion + +Publication verification은 internal network의 disposable PostgreSQL, synthetic credential과 synthetic encryption key만 사용해 별도 pre-start `pfhub migrate-db`를 head까지 성공시킨 뒤 application을 시작해야 한다. Alembic revision, 별도 `pfhub health-check` DB ping, DB-enabled `/health` liveness를 각각 확인하고 성공·timeout·wrong-shape·canary/scan failure에서 실제 시도한 container, network 및 volume identity를 정확히 정리해야 한다. + +## Verification + +- 성공 조건: migration이 head에 도달하고 DB ping과 HTTP liveness가 각각 통과하며 생성·시도한 exact resource가 모두 cleanup된다. +- 실패·경계 조건: production credential/DB 사용, application startup 중 암묵적 migration, bootstrap-user 비격리 side effect, readiness/liveness 혼합, timeout 또는 wrong-shaped payload 후 resource leak은 실패다. +- 검사 방법: actual local image/PostgreSQL startup smoke, timeout/wrong-shape regressions, canary-bearing `docker create` cleanup regression과 exact attempted/removed identity assertions를 실행한다. diff --git a/.proofline/criteria/ac-0012.md b/.proofline/criteria/ac-0012.md new file mode 100644 index 0000000..a5604dd --- /dev/null +++ b/.proofline/criteria/ac-0012.md @@ -0,0 +1,16 @@ +--- +id: "ac-0012" +status: active +--- + +# Immutable operator handoff와 deployment exclusion + +## Criterion + +Container operations 문서는 게시 산출물의 immutable `image@sha256:` identity, source revision, 별도 pre-start `pfhub migrate-db`, runtime secret injection, read-only root filesystem, 명시적 writable mount를 정의해야 한다. Image rollback은 이전 recorded digest의 재선택이고 DB migration rollback·data recovery와 같지 않음을 명시하며 운영 deployment 기능·설정·실행 명령을 포함하지 않아야 한다. + +## Verification + +- 성공 조건: 운영자가 publication output에서 immutable artifact와 필수 runtime/pre-start 입력 경계를 식별할 수 있고 deployment·DB rollback은 별도 승인 범위로 명확히 분리된다. +- 실패·경계 조건: moving tag를 rollback identity로 제시하거나 image rollback이 DB 상태를 되돌린다고 암시하거나 Compose/Kubernetes/Helm/systemd/Dockge 배포 구성·실행·재시작 명령을 포함하면 실패다. +- 검사 방법: `docs/container-operations.md` contract test로 digest, migration, secret/read-only/writable mount, image≠DB rollback 및 deployment-command 부재를 검사한다. diff --git a/.proofline/identities.json b/.proofline/identities.json index f9b6298..d7e4631 100644 --- a/.proofline/identities.json +++ b/.proofline/identities.json @@ -1,5 +1,5 @@ { "schema_version": 1, - "next_line_number": 2, - "next_ac_number": 6 + "next_line_number": 3, + "next_ac_number": 13 } diff --git a/.proofline/lines/line-0002/dcy-0002.md b/.proofline/lines/line-0002/dcy-0002.md new file mode 100644 index 0000000..9f9455c --- /dev/null +++ b/.proofline/lines/line-0002/dcy-0002.md @@ -0,0 +1,307 @@ +--- +id: "dcy-0002" +status: 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 이슈도 같은 방향을 가리킨다. + +- `trex-core` #57: https://github.com/fourblocks2020/trex-core/issues/57 +- `kis-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`는 다음을 동시에 만족해야 한다. + +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를 확인한 결과: + +```text +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:` + - `ghcr.io/fourblocks2020/portfolio-hub:vX.Y.Z` +- 실제 deploy/rollback identity는 `ghcr.io/fourblocks2020/portfolio-hub@sha256:` +- 기본 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로 채택할 것인가? + +- [x] **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에서 검증 +- [x] **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 확인 + +- [x] **OQ-3-A (승인):** 위 pre-start migration 순서를 운영 계약으로 고정 +- [ ] **OQ-3-B:** application startup 과정에서 migration을 수행 + +결정 근거: **A**는 application startup이 production schema mutation을 암묵적으로 수행하지 않게 한다. + +#### OQ-4 — runtime entrypoint (확정) + +- [x] **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 (확정) + +- [x] **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에서 어떤 검증을 수행할 것인가? + +- [x] **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 승인으로 확정되었다. + +- [x] OQ-1 migration payload/resource layout +- [x] OQ-2 PostgreSQL publication smoke 깊이 +- [x] OQ-3 pre-start migration 운영 순서 +- [x] OQ-4 runtime entrypoint +- [x] OQ-5 Python minor baseline +- [x] OQ-6 branch CI와 publication workflow 분리 방식 +- [x] image name 및 selector 규칙 +- [x] production writable mount 및 secret injection 경계 +- [x] 구현이 tag push/publication/deployment를 자동 승인하지 않는다는 외부-write 경계 + +### Discovery 성공 조건 + +다음 산출이 완료되면 요구사항/implementation scope를 제안할 수 있다. + +- [x] 비교 대상 세 서비스의 현재 방식과 목표 방식 차이가 위 근거와 일치한다. +- [x] `portfolio-hub`의 migration payload 실패 seam을 재현 가능한 contract로 정의한다. +- [x] OQ-1~OQ-6이 requester 결정으로 해소된다. +- [x] HTTP liveness, DB readiness, schema migration, publication, deployment의 상태가 서로 분리된다. +- [x] 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 승인 없이는 수행하지 않는다. diff --git a/.proofline/lines/line-0002/evidence/implementation-complete.md b/.proofline/lines/line-0002/evidence/implementation-complete.md new file mode 100644 index 0000000..37e9b37 --- /dev/null +++ b/.proofline/lines/line-0002/evidence/implementation-complete.md @@ -0,0 +1,50 @@ +# 구현 완료 — GitHub Issue #25 비정식 증적 + +> 이 문서는 ProofLine canonical lifecycle artifact가 아니다. #25 구현 당시 GitHub 댓글을 출처와 함께 원문 보존한 비정식 증적이다. +> 이 증거는 로컬 구현·검증 상태를 기록하지만 ProofLine 정식 implementation chronology, delivery closure, push, publication, release 또는 deployment 완료를 소급 증명하지 않는다. +> Source: https://github.com/fourblocks2020/portfolio-hub/issues/25#issuecomment-5277346458 +> Comment ID: `IC_kwDOR4l8PM8AAAABOo3qmg` +> Author: `genichin` +> Created: `2026-08-13T07:27:30Z` +> Original body SHA-256: `e71adc00bfec74b8802134a154876e6325e051e8b10d8d3fff79f51aa48042d6` + +--- + +## 구현 완료 + +`portfolio-hub` #25에서 승인한 requester-versioned GHCR 이미지 게시 기능을 구현하고 로컬 검증과 독립 리뷰까지 완료했습니다. + +### 구현 내용 + +- Alembic 설정과 migration 28개를 installable package data로 이전하고 `importlib.resources` 기반 조회 및 source↔wheel/image inventory parity를 구현했습니다. +- Python 3.11·`uv.lock` 기반 multi-stage non-root(`10001:10001`) Docker image와 deny-by-default `.dockerignore`를 추가했습니다. +- branch/main 검증과 registry publication을 분리하고, strict `vX.Y.Z` tag push만 GHCR 게시 권한을 갖도록 구성했습니다. +- build-once tested artifact handoff, image ID, archive SHA-256, OCI revision, secret canary, no-rebuild publish, commit/version selector same-digest 검증을 구현했습니다. +- installed payload, DB-disabled `/health`, isolated PostgreSQL migration·DB ping, DB-enabled `/health` 및 exact-resource cleanup smoke를 구현했습니다. +- 게시 산출물의 digest identity, pre-start `pfhub migrate-db`, secret/read-only/writable mount 및 image rollback≠DB rollback 경계를 문서화했습니다. 운영 배포 기능·설정·실행은 포함하지 않았습니다. +- 로컬 커밋 `96260b6302e050c6e564f3eca4e723558f26246f`(`feat(release): add verified image publication pipeline`)으로 구현 스냅샷을 고정했습니다. + +### 검증 결과 + +최종 스냅샷에서 코드·패키지·이미지·DB smoke와 독립 재리뷰가 모두 통과했습니다. + +- Release focused tests: **23 passed** +- 전체 pytest: **621 passed, 6 skipped** +- `uv lock --check`: 통과 +- Scoped Ruff: 통과 +- `git diff --check`: 통과 +- 실제 Python 3.11 local image build 및 canary scan: 통과 +- 실제 installed-image/PostgreSQL startup smoke: 통과 +- 이미지 UID/GID `10001:10001`, OCI revision과 기준 SHA 일치, migration 28개 및 최신 `0028` 포함 확인 +- 최초 독립 리뷰 FAIL의 MAJOR 1건·MINOR 1건을 수정하고 영구 회귀 테스트를 추가함 +- 독립 재리뷰: **PASS**, 기존 발견 모두 CLOSED, 새 회귀·배포 범위 확장 없음 +- `.proofline` 변경 없음 + +### 비차단 후속 권고 + +- 원격 반영이 필요할 때 별도 requester 지시에 따라 기능 브랜치 push와 PR 생성 여부를 결정합니다. +- 원격 통합 후 branch/main hosted CI 결과를 exact commit 기준으로 확인합니다. +- 실제 `vX.Y.Z` tag push와 GHCR publication은 별도 명시 승인 후 수행하고, exact tag/SHA workflow 및 registry digest를 확인합니다. +- 운영 배포·재시작·production DB migration은 계속 별도 범위와 별도 승인으로 유지합니다. + +> 현재 상태는 **로컬 구현·검증·커밋 완료**입니다. 푸시·PR·이미지 게시·배포·릴리스 완료를 의미하지 않습니다. diff --git a/.proofline/lines/line-0002/evidence/implementation-progress.md b/.proofline/lines/line-0002/evidence/implementation-progress.md new file mode 100644 index 0000000..87d3ba8 --- /dev/null +++ b/.proofline/lines/line-0002/evidence/implementation-progress.md @@ -0,0 +1,53 @@ +# 구현 진행 상황 — GitHub Issue #25 비정식 증적 + +> 이 문서는 ProofLine canonical lifecycle artifact가 아니다. #25 구현 당시 GitHub 댓글을 출처와 함께 원문 보존한 비정식 증적이다. +> 이 증거는 로컬 구현·검증 상태를 기록하지만 ProofLine 정식 implementation chronology, delivery closure, push, publication, release 또는 deployment 완료를 소급 증명하지 않는다. +> Source: https://github.com/fourblocks2020/portfolio-hub/issues/25#issuecomment-5277080186 +> Comment ID: `IC_kwDOR4l8PM8AAAABOonaeg` +> Author: `genichin` +> Created: `2026-08-13T06:57:02Z` +> Original body SHA-256: `1b616d14c9f05a75da1345ef58b7a69a05b1bf2e62b79063476490b758e06bd0` + +--- + +## 구현 진행 상황 + +- **대상:** discovery: requester-versioned GHCR 이미지 게시 계약 +- **브랜치:** `feat/25-ghcr-image-publication` +- **기준:** `origin/main` (`35e85981af845489236c18d709d0545c830e7f03`) +- **상태:** 구현·검증·독립 리뷰 완료 — 로컬 커밋 완료, push/PR/publication/배포 미수행 +- **최종 업데이트:** `2026-08-13T16:23:22+09:00` + +### 구현 계획 및 작업 목록 + +- [x] migration/Alembic resource를 installable package data로 전환하고 source↔wheel inventory parity를 검증한다. +- [x] Python 3.11·`uv.lock` 기반 multi-stage non-root Docker image와 deny-by-default build context를 구현한다. +- [x] strict `vX.Y.Z` tag-only, build-once, tested-artifact handoff, no-rebuild GHCR publication workflow를 구현한다. +- [x] image ID·archive SHA-256·OCI revision·canary·commit/version selector same-digest fail-closed helper와 contract tests를 구현한다. +- [x] installed payload, DB-disabled `/health`, isolated PostgreSQL migration + DB ping smoke와 cleanup 검증을 구현한다. +- [x] branch/main 무게시 검증 CI와 tag publication 권한 경계를 구현한다. +- [x] 게시된 image digest와 pre-start `pfhub migrate-db`, secret/read-only/writable mount의 operator handoff 경계를 문서화한다(배포 구현 제외). +- [x] focused release tests와 전체 pytest, lock/diff hygiene 및 실제 local image smoke를 통과한다. +- [x] 독립 코드 리뷰 결과를 반영하고 최종 작업 상태를 기록한다. + +### 진행 기록 + +- `2026-08-13T15:56:37+09:00` — 승인된 OQ(`1-A`, `2-B`, `3-A`, `4-A`, `5-A`, `6-A`)를 구현 범위로 고정했다. +- `2026-08-13T15:56:37+09:00` — 최신 원격 `origin/main` `35e85981af845489236c18d709d0545c830e7f03`에서 격리 worktree와 기능 브랜치를 생성했다. +- PR 생성, tag push, 실제 GHCR publication, GitHub Release 및 운영 배포는 이 구현 실행 범위에 포함하지 않는다. +- `2026-08-13T15:58:13+09:00` — requester 확인에 따라 운영 배포 기능·배포 설정·배포 실행을 명시적으로 제외했다. digest 관련 문서는 게시 산출물의 operator handoff identity와 image rollback 경계 설명에만 한정한다. +- `2026-08-13T16:05:01+09:00` — release focused tests `22 passed`, scoped Ruff 통과. +- `2026-08-13T16:06:34+09:00` — migration 경로 변경에 따른 기존 테스트를 정합화한 뒤 전체 pytest 통과. +- `2026-08-13T16:07:00+09:00` — Python 3.11 locked non-root 로컬 이미지 build 성공. +- `2026-08-13T16:07:26+09:00` — 구현 runner가 600초 제한으로 timeout 종료했다. 변경은 worktree에 보존되었고, 마지막 installed inventory/image smoke는 중단되어 미검증 상태다. 다음 담당은 팀장이며 diff 검토, 배포 문서 범위 보정, 실제 image smoke와 독립 리뷰를 계속한다. +- `2026-08-13T16:08:49+09:00` — 보존된 로컬 이미지에서 installed inventory, DB-disabled `/health`, isolated PostgreSQL migration·DB ping, DB-enabled `/health`, non-root, cleanup smoke를 재실행해 exit 0 확인. +- `2026-08-13T16:10:50+09:00` — 배포 범위를 넘던 실행 예시를 제거하고 publication artifact/operator handoff 계약만 남겼다. release contract에서 배포 명령 부재를 회귀 검증한다. +- `2026-08-13T16:10:50+09:00` — 최종 스냅샷에서 `uv lock --check`, scoped Ruff, release `22 passed`, 전체 `620 passed, 6 skipped`, `git diff --check` 통과. 이미지 UID/GID `10001:10001`, OCI revision 기준 SHA 일치, installed migration 28개(0028 포함), `.proofline` 변경 없음 확인. +- `2026-08-13T16:16:37+09:00` — 독립 리뷰 FAIL(1 MAJOR, 1 MINOR, 0 BLOCKER). MAJOR: canary가 `docker create` 출력에서 검출될 때 생성된 scan container cleanup이 누락될 수 있음. MINOR: startup cleanup tests가 정확한 attempted/removed resource identity 대신 호출 개수만 검사함. +- `2026-08-13T16:20:45+09:00` — MAJOR를 수정해 `docker create` ID를 canary 검사 전에 보존하고 모든 post-create 실패에서 정확한 ID cleanup을 보장했다. MINOR를 수정해 attempted/removed container·network·volume identity가 정확히 일치하도록 구현·테스트를 강화했다. +- `2026-08-13T16:21:20+09:00` — 보정 후 release `23 passed`, 전체 `621 passed, 6 skipped`, 실제 image canary scan 및 startup smoke, lock·Ruff·diff 검증 통과. +- `2026-08-13T16:22:58+09:00` — 독립 재리뷰 PASS. 기존 MAJOR/MINOR 모두 CLOSED, 새 회귀 및 배포 범위 확장 없음. +- `2026-08-13T16:23:22+09:00` — 로컬 커밋 `96260b6302e050c6e564f3eca4e723558f26246f`(`feat(release): add verified image publication pipeline`) 생성. 작업트리 clean 확인. +- 구현·검증·독립 리뷰는 완료했다. 기능 브랜치 push, PR, tag push, 실제 GHCR publication, GitHub Release 및 운영 배포는 수행하지 않았다. + +> 각 작업이 실제로 완료되고 검증될 때마다 이 댓글의 체크박스와 진행 기록을 갱신한다. 체크 표시는 구현 완료 또는 해당 검증의 실제 통과를 의미하며, 배포·릴리스 완료를 의미하지 않는다. diff --git a/.proofline/lines/line-0002/line-0002.md b/.proofline/lines/line-0002/line-0002.md new file mode 100644 index 0000000..1408721 --- /dev/null +++ b/.proofline/lines/line-0002/line-0002.md @@ -0,0 +1,3 @@ +--- +id: "line-0002" +--- diff --git a/.proofline/lines/line-0002/req-0002.md b/.proofline/lines/line-0002/req-0002.md new file mode 100644 index 0000000..072b8dc --- /dev/null +++ b/.proofline/lines/line-0002/req-0002.md @@ -0,0 +1,48 @@ +--- +id: "req-0002" +status: approved +discovery: "dcy-0002" +criteria: + create: + - "ac-0006" + - "ac-0007" + - "ac-0008" + - "ac-0009" + - "ac-0010" + - "ac-0011" + - "ac-0012" + update: [] + retire: [] + satisfy: [] +--- + +# Requester-versioned GHCR 이미지 게시 계약 + +## Objective + +`portfolio-hub`가 requester가 명시적으로 선택한 strict `vX.Y.Z` tag의 exact source에서 검증된 OCI image를 한 번만 build하고, 설치 payload·HTTP·PostgreSQL migration/readiness·비밀 제외·artifact identity를 검증한 동일 artifact만 GHCR에 게시할 수 있는 fail-closed capability를 제공한다. + +## Scope + +- Alembic 설정과 모든 migration을 installable package data로 포함하고 source, wheel 및 image inventory의 non-empty exact parity를 유지한다. +- Python 3.11과 repository-owned `uv.lock`을 verification 및 runtime dependency 설치에 사용하며 non-root runtime과 deny-by-default build context를 제공한다. +- 일반 branch/main은 registry write 권한 없이 full regression과 release/container contract를 검증하고, strict `vX.Y.Z` tag push만 publication authority와 `packages: write`를 가진다. +- verify 단계가 exact tagged commit에서 image를 한 번 build하고 installed payload, DB-disabled HTTP liveness, isolated PostgreSQL migration·DB ping·DB-enabled liveness 및 cleanup을 검증한다. +- tested image ID, archive SHA-256, OCI revision과 secret canary를 fail-closed로 검사하고, publish 단계는 같은 archive를 checksum 검증 후 load하여 rebuild 없이 source selector와 requester version selector에 게시하며 same digest를 확인한다. +- 운영자에게 immutable digest, 명시적 pre-start `pfhub migrate-db`, runtime secret/read-only/writable mount 및 image rollback과 DB rollback의 분리 경계를 문서화한다. + +## Constraints + +- automatic version bump, malformed/prerelease tag publication, moving `latest`, branch/main registry write를 허용하지 않는다. +- 실제 credential, production DB, production migration 및 bootstrap-user side effect를 smoke에 사용하지 않는다. +- image build·검증·publication capability는 운영 deployment 기능·설정·실행과 분리한다. +- registry login/push 전 검증 실패는 external registry write 전에 fail-closed여야 한다. push 이후에만 얻는 digest가 없거나 malformed이면 workflow는 nonzero로 실패하고 성공 summary를 내지 않지만 이미 발생한 registry write를 없었다고 주장하지 않는다. +- 이 승인된 사양은 로컬 구현 커밋 `96260b6302e050c6e564f3eca4e723558f26246f`의 의도를 기술하지만, 사후 ProofLine 문서화 자체가 정식 implementation chronology, delivery closure, push, publication, release 또는 deployment 완료를 소급 생성하지 않는다. + +## Non-Goals + +- 이 Requirement에서 version 선택, Git tag 생성·push, 기능 브랜치 push, PR, GHCR publication 또는 package visibility 변경을 실행하지 않는다. +- GitHub Release, 운영 서버/Dockge/Compose 배포·재시작, production credential·DB·migration·downgrade·repair·reset·backup을 수행하지 않는다. +- deployment orchestrator, multi-architecture build, signing, SBOM, attestation을 추가하지 않는다. +- application API/domain behavior, sibling repository 또는 unrelated repository-wide lint debt를 변경하지 않는다. +- ProofLine 정식 시퀀스로 구현됐다는 소급 주장을 만들지 않는다.