Add ProofLine criteria documentation and Strategy Catalog v1 implementation

- Add acceptance criteria documentation (AC-0013 through AC-0023)
- Add Line-0003 discovery documentation
- Implement Strategy Catalog v1 with new database migration
- Update portfolio API endpoints and schemas
- Refactor strategy and portfolio services
- Add strategy management CLI commands
- Update domain models with strategy catalog support
- Add comprehensive tests for v1 strategies
- Update design documentation (data model, interfaces, runtime flows)
This commit is contained in:
2026-08-15 11:18:31 +09:00
parent 5fc389dea8
commit 6e02d92282
48 changed files with 2114 additions and 393 deletions
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0013"
status: active
---
# active 전략 일반 조회 계약
## Criterion
일반 사용자와 admin은 `/v1/strategies`의 목록·상세 조회에서 active 전략만 받을 수 있어야 하며, inactive 전략은 목록에서 제외하고 상세에서 전략 본문 대신 `409`를 반환하고 미존재 전략은 `404`를 반환해야 한다.
## Verification
- 성공 조건: active 전략은 목록과 상세에서 안정적인 ID, 이름, config, status와 revision을 반환한다.
- 실패·경계 조건: inactive 전략은 목록에서 제외되고 일반 상세 조회는 전략 본문 대신 `409`를 반환하며, 미존재 ID는 `404`다.
- 검사 방법: 일반·admin token의 목록/상세 API 회귀 테스트로 active/inactive/missing을 각각 검사한다.
- 현재 구현 평가: working tree의 `app/api/v1_strategies.py`와 `tests/test_v1_strategies.py`가 이 계약을 실행 가능하게 검증하며 scoped 재실행에 포함되어 PASS했다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0014"
status: active
---
# 관리자 전략 카탈로그 권한과 관리 계약
## Criterion
admin은 `/v1/admin/strategies`에서 active·inactive 전체 전략을 조회하고 생성·수정·상태 변경·미참조 삭제할 수 있어야 하며, 비-admin 사용자의 모든 관리 mutation은 `403`이어야 한다.
## Verification
- 성공 조건: admin CRUD와 상태 변경이 영속화되고 관리 조회가 active·inactive를 모두 반환한다.
- 실패·경계 조건: 일반 사용자 또는 권한 부족 token의 create/update/delete는 변경 없이 `403`이다.
- 검사 방법: 역할별 token으로 admin 목록·생성·PATCH·DELETE와 비-admin 거부를 API 테스트로 검사한다.
- 현재 구현 평가: working tree의 모든 admin route가 공통 `require_admin` dependency를 사용하고 admin CRUD tests 및 비-admin 관리 GET `403` test가 scoped 재실행에서 PASS했다. 비-admin POST/PATCH/DELETE 각각의 독립 negative test는 현재 없어 mutation별 executable coverage는 `PARTIAL`이다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0015"
status: active
---
# 공유 전략 identity와 revision 계약
## Criterion
admin PATCH는 기존 전략 row의 ID를 유지하면서 요청된 `name`·`config`·`status`의 실질적 변경을 원자적으로 반영하고 revision을 증가시켜야 하며, 모든 참조 포트폴리오의 이후 portfolio-hub API 조회는 commit된 최신 설정과 revision을 반환해야 한다.
## Verification
- 성공 조건: 실질적 PATCH 뒤 ID는 동일하고 revision은 단조 증가하며 derived `nick`과 name/config가 같은 committed state에서 계산되고 참조 포트폴리오 조회에 최신 값이 나타난다.
- 실패·경계 조건: no-op PATCH는 revision을 올리지 않고, 동시 partial PATCH도 서로의 최신 필드를 잃거나 불일치한 `nick`을 만들지 않는다.
- 검사 방법: API/repository 회귀와 실제 PostgreSQL 동시 partial PATCH 시험으로 ID, revision, name/config/nick 및 포트폴리오 응답을 검사한다.
- 현재 구현 평가: `FOR UPDATE` 기반 update와 revision 응답 테스트가 존재하며, 2026-08-15 실제 PostgreSQL integration 재실행 `4 passed`에 동시 partial PATCH 검증이 포함됐다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0016"
status: active
---
# 전략 상태와 포트폴리오 선택 계약
## Criterion
신규 포트폴리오 생성·수정은 전역 카탈로그의 기존 active `strategy_id`만 선택할 수 있어야 하며, 전략을 inactive로 전환해도 기존 포트폴리오 참조는 보존되고 신규 선택만 차단되어야 한다.
## Verification
- 성공 조건: active ID 선택은 저장되고 inactive 전환 전의 기존 참조는 조회와 저장에서 유지된다.
- 실패·경계 조건: inactive ID 신규 선택은 `409`, 미존재 ID는 `404`이며 legacy `avabile_strategies`만으로 선택 권한을 얻지 못한다.
- 검사 방법: create/update 및 inactive 전환 전후 포트폴리오 회귀 테스트와 선택·상태 변경 경합 테스트로 확인한다.
- 현재 구현 평가: portfolio service/repository와 API 회귀가 scoped 재실행에서 PASS했고, 2026-08-15 실제 PostgreSQL integration `4 passed`에 선택-vs-delete 경합 검증이 포함됐다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0017"
status: active
---
# 포트폴리오 ID-only 요청과 응답 호환성 계약
## Criterion
`/v1/portfolios`와 legacy `/portfolios`의 생성에는 active `strategy_id`가 필수이고 수정도 ID-only여야 하며, 요청의 `strategy_name`·`strategy_params`는 `422`로 거부하는 동시에 응답의 `active_strategy_id`·`strategy_name`·`strategy_params`·`strategy_revision`은 유지되어야 한다.
## Verification
- 성공 조건: 명시한 active ID로만 포트폴리오가 생성·수정되고 응답에 현재 전략 identity·이름·config·revision이 나타난다.
- 실패·경계 조건: strategy ID 누락, 제거 필드 전달 또는 malformed 요청은 새 전략 row를 만들지 않고 `422`다.
- 검사 방법: v1·legacy create/update schema와 저장소 row count, 응답 호환 필드를 API/CLI 회귀로 검사한다.
- 현재 구현 평가: schemas와 양쪽 portfolio API가 ID-only 입력 및 `extra="forbid"` 계약을 구현하고 scoped portfolio/CLI 회귀는 PASS했다. 다만 제거된 `strategy_name`·`strategy_params` 각각의 `422`와 응답 호환 필드 전체를 직접 단언하는 전용 negative/response test는 현재 없어 해당 executable coverage는 `PARTIAL`이다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0018"
status: active
---
# 전략 API 오류 분류 계약
## Criterion
전략 조회·선택·관리 API는 미존재 `404`, inactive 신규 선택과 참조 중 삭제 `409`, 제거 필드·malformed 입력 `422`, 권한 부족 `403`을 서로 구분해 반환해야 한다.
## Verification
- 성공 조건: 각 오류 조건이 승인된 상태 코드와 안정적인 비민감 오류 응답으로 구분된다.
- 실패·경계 조건: 권한 부족을 `404`로, inactive를 `422`로, malformed 입력을 묵시적으로 무시하는 등 오류 클래스가 합쳐지지 않는다.
- 검사 방법: 조건별 독립 API negative test로 status code와 무변경을 검사한다.
- 현재 구현 평가: `tests/test_v1_strategies.py`와 `tests/test_v1_portfolios_crud.py`의 negative matrix가 scoped 재실행에서 PASS했다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0019"
status: active
---
# 참조 안전 전략 삭제 계약
## Criterion
전략 hard delete는 어떤 포트폴리오의 현재 `active_strategy_id` 또는 현재 선택 참조에도 없는 경우에만 허용되어야 하며, 참조 중이면 자동 이전·참조 삭제 없이 `409`를 반환해야 한다.
## Verification
- 성공 조건: 미참조 전략은 admin이 삭제할 수 있고 삭제 후 조회되지 않는다.
- 실패·경계 조건: 어떤 지원 참조 표현이라도 전략을 가리키면 `409`이며 포트폴리오 참조는 그대로다; 동시 신규 선택과 삭제에서도 orphan이 생기지 않는다.
- 검사 방법: 두 참조 형태별 삭제, retirement 대안, 선택-vs-delete 실제 PostgreSQL 경합을 검사한다.
- 현재 구현 평가: `FOR UPDATE`와 참조 검사·삭제의 단일 transaction 및 회귀 테스트가 있으며, 2026-08-15 실제 PostgreSQL integration `4 passed`에서 선택-vs-delete orphan 방지를 재검증했다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0020"
status: active
---
# v1 전략 API 전환과 운영자 CLI 무결성 계약
## Criterion
전략 HTTP API는 `/v1/strategies`와 `/v1/admin/strategies`만 제공하고 비버전 `/strategy-sets` alias를 제공하지 않아야 하며, 신뢰된 운영자 CLI도 상태·revision·참조 삭제 무결성을 우회해서는 안 된다.
## Verification
- 성공 조건: OpenAPI/router inventory에 새 v1 경로가 있고 CLI 관리 결과가 HTTP와 같은 row identity·status·revision·delete guard를 따른다.
- 실패·경계 조건: `/strategy-sets` 요청은 기존 CRUD로 동작하지 않고, CLI가 참조 중 삭제나 잘못된 상태 전이를 강행하지 않는다.
- 검사 방법: route inventory, legacy path negative test, CLI create/list/update/delete 회귀와 문서 경로 sweep을 수행한다.
- 현재 구현 평가: legacy router 제거, v1 router 등록 및 CLI tests가 존재하며 scoped 재실행에서 PASS했다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0021"
status: active
---
# 기본 전략 migration 멱등 계약
## Criterion
Alembic data migration은 active `name="target_value"`, `config={}` 전략을 중복 없이 보장하고, 동일 `(name, config)` row가 있으면 재사용해야 하며 포트폴리오 요청의 `strategy_id` 필수 조건을 완화하지 않아야 한다.
## Verification
- 성공 조건: 빈 DB upgrade 후 기본 전략 한 건이 active, revision 1과 runtime 일치 derived identity로 존재한다.
- 실패·경계 조건: migration 재실행이나 기존 동일 row가 중복 row를 만들지 않고, 기본 전략 존재가 implicit portfolio selection을 허용하지 않는다.
- 검사 방법: migration unit/idempotency test와 신규 PostgreSQL DB full upgrade/head 재실행, portfolio create negative test를 수행한다.
- 현재 구현 평가: migration unit test가 scoped 재실행에서 PASS했고, 2026-08-15 실제 PostgreSQL integration `4 passed`에서 신규 DB full upgrade와 head 재실행을 재검증했다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0022"
status: active
---
# downstream 슬롯 적용 시점 경계
## Criterion
portfolio-hub에 commit된 전략 변경은 이후 portfolio-hub API 조회에 즉시 보이되 실행 중 trex-core 슬롯·기존 주문·완료 거래에 소급 적용되지 않아야 하며, trex-core는 다음 슬롯 재생성 또는 정기 갱신에서 최신 revision을 가져와 이후 동작에 적용해야 한다.
## Verification
- 성공 조건: portfolio-hub 조회는 최신 revision을 제공하고 portfolio-hub가 daemon reload/restart 또는 config push를 발생시키지 않으며, trex-core의 다음 refresh가 최신 revision을 선택한다.
- 실패·경계 조건: PATCH가 실행 중 슬롯을 강제 변경하거나 기존 주문·완료 거래를 재해석하지 않고, 정확한 일일 scheduler 시각은 이 Line이 정하지 않는다.
- 검사 방법: portfolio-hub API/runtime-flow 문서와 outbound side-effect 부재를 검사하고, 별도 trex-core integration에서 refresh 전/후 revision 적용을 검증한다.
- 현재 구현 평가: portfolio-hub 측 최신 revision 노출과 강제 push/reload 부재는 working tree에 반영되어 있다. trex-core refresh end-to-end 검증은 이 저장소에 executable seam이 없어 현재 `UNVERIFIED`이며, `active` 상태를 satisfaction으로 해석하지 않는다.
+17
View File
@@ -0,0 +1,17 @@
---
id: "ac-0023"
status: active
---
# 동시성 및 stale persistence 무결성 계약
## Criterion
전략 PATCH, 포트폴리오 전략 선택, 전략 상태 변경·삭제와 거래 저장이 경합해도 최신 전략 참조와 revision은 직렬화 가능하게 보존되고, stale portfolio snapshot은 현재 전략 선택을 되살리거나 덮어쓰지 않으며 portfolio mutation과 ledger 기록은 원자적이어야 한다.
## Verification
- 성공 조건: 동시 partial PATCH는 모든 변경과 단조 revision을 보존하고, A snapshot 후 B 선택·A 삭제·stale 거래 저장에서도 B 참조와 A 삭제가 유지되며 portfolio UPDATE와 ledger INSERT가 함께 commit된다.
- 실패·경계 조건: stale write가 삭제된 A를 복원하거나 B를 잃지 않고, transaction 실패 시 portfolio와 ledger가 부분 commit되지 않는다.
- 검사 방법: 실제 PostgreSQL에서 동시 PATCH, select-vs-inactivate/delete, stale BUY/SELL snapshot과 transaction rollback 시나리오를 실행한다.
- 현재 구현 평가: repository transaction/row-lock 경계와 integration tests가 존재하며, 2026-08-15 실제 PostgreSQL integration 재실행 `4 passed`에서 동시 PATCH, 선택-vs-delete, stale BUY snapshot과 migration 시나리오를 확인했다. 독립 source-time QC도 PASS를 기록한다. 다만 portfolio UPDATE 성공 후 ledger INSERT 실패를 의도적으로 주입해 rollback을 관찰하는 fault-injection test는 현재 없어 그 세부 검증은 `PARTIAL`이다.
+2 -2
View File
@@ -1,5 +1,5 @@
{
"schema_version": 1,
"next_line_number": 3,
"next_ac_number": 13
"next_line_number": 4,
"next_ac_number": 24
}
+184
View File
@@ -0,0 +1,184 @@
---
id: "dcy-0003"
status: confirmed
---
# 전략 API v1 이전 및 관리자 전역 카탈로그 권한 분리
## Problem
`strategy_sets`는 전역 공유 카탈로그이지만 비버전 `/strategy-sets` 쓰기 API와 포트폴리오의 `get_or_create_strategy_set()` 간접 생성 경로가 일반 `trade-write` 사용자에게 열려 있었다. 전략 정의의 관리자 관리 책임, 일반 사용자의 기존 active 전략 선택 책임, 공유 설정 저장 시점과 downstream 실행 슬롯 적용 시점이 분리되어야 한다.
본 문서는 이미 요청자 승인을 받은 GitHub 이슈 #26을 ProofLine에 사후 포착한다. 아래 `Scope`의 **승인된 이슈 원문 전체**는 구조적 heading만 canonical H2 충돌을 피하도록 한 단계 낮췄으며, 원문 비어 있지 않은 모든 행을 순서대로 보존한다.
## Evidence
### 원문·승인·사후 문서화 경계
- 원문: https://github.com/fourblocks2020/portfolio-hub/issues/26
- 원문 제목: `전략 API v1 이전 및 관리자 전역 카탈로그 권한 분리`
- 원문 상태: `OPEN`
- 원문 body SHA-256: `011ece78747cac8e0c38ad07b644ffef080ea1fb7480eef77380c44724b9727e`
- 원문 자체 승인 기록: `APPROVED`, 승인자 `genichin`, 승인 시각 `2026-08-14T08:14:31Z`, unresolved Open Questions 없음.
- 요청자는 이슈 #26 본문을 최대한 포함한 `confirmed` Discovery와 `approved` REQ, 구현 상태를 반영한 `active` AC를 새 Line에 사후 작성하도록 명시했다.
- 이 Line은 이미 수행된 로컬 구현을 승인된 지속 사양으로 포착한다. #26 구현이 ProofLine 정식 시퀀스로 수행됐다는 주장, ProofLine implementation/delivery closure, push, PR, 원격 main 반영, release 또는 deployment 완료를 소급 생성하지 않는다.
### 현재 구현·검증 증거
- 구현 기준: `main` `5fc389dea8f05c6f711ecfe874f055979af5e58d`
- 구현 형태: 기준 HEAD 위 로컬 `main` working tree의 미커밋 product/test/doc 변경.
- 구현 진행 댓글: https://github.com/fourblocks2020/portfolio-hub/issues/26#issuecomment-5291151212
- 구현 완료 댓글: https://github.com/fourblocks2020/portfolio-hub/issues/26#issuecomment-5291927258
- 댓글이 기록한 최종 검증: 이슈 집중 회귀 `99 passed`; 실제 PostgreSQL 동시성·migration 통합 `4 passed`; 관련 회귀 `75 passed`; 알려진 기존 외부 오염 파일 제외 광범위 회귀 `608 passed, 3 skipped`; 독립 최종 QC `PASS`.
- 2026-08-15 현재 재검증: scoped API·migration·portfolio·CLI suite `70 passed, 4 skipped`; 별도 실제 PostgreSQL 동시성·migration integration `4 passed`.
- 두 댓글의 exact body는 이 Line의 `evidence/implementation-progress.md`와 `evidence/implementation-complete.md`에 비정식 증적으로 보존한다.
- 위 결과는 source-time 구현 증거다. `active` AC는 normative 상태이며, 그 자체가 현재 commit-bound satisfaction이나 ProofLine delivery closure를 뜻하지 않는다.
## Scope
### 승인된 이슈 원문 전체
### 배경
현재 전략 설정 CRUD는 비버전 경로인 `/strategy-sets`에 있고, 포트폴리오·자산·태그·데몬 등 주요 제품 API는 `/v1` 아래에 있다. `strategy_sets`는 사용자 소유 리소스가 아닌 전역 공유 카탈로그지만, 현재 쓰기 API는 `trade-write` 권한을 허용한다.
`/v1/portfolios`의 생성·수정 요청은 `strategy_name`과 `strategy_params`를 받아 `get_or_create_strategy_set()`을 호출하므로, 전략 관리 API만 제한하면 일반 사용자의 간접 생성 경로가 남는다.
### 확인 근거
- `app/api/strategy_sets.py`: 비버전 `/strategy-sets`와 `trade-write` 쓰기 권한
- `app/api/v1_portfolios.py`: 생성·수정 시 `get_or_create_strategy_set()`을 통한 간접 생성
- `app/repositories/postgres.py`: 전역 `strategy_sets` 저장과 포트폴리오 참조·삭제 방어
- `app/schemas/v1.py`: 포트폴리오 요청의 `strategy_name`·`strategy_params`, 응답의 `active_strategy_id`·`strategy_name`·`strategy_params`
- `app/cli/commands/strategy.py`: 운영자 CLI의 직접 저장소 관리 경로
- 워크스페이스 소비자 조사: trex-core와 전략 daemon은 `/v1/portfolios` 응답의 `strategy_name`·`strategy_params`를 소비한다. `/strategy-sets`를 직접 호출하는 다른 repository 코드는 발견되지 않았다.
### 문제
1. 전략 API만 비버전 경로에 남아 있어 API 계약 체계가 일관되지 않는다.
2. 일반 `trade-write` 사용자가 전역 전략 설정을 생성·수정·삭제할 수 있다.
3. 포트폴리오 생성·수정 API를 통한 전략 설정의 간접 생성이 가능하다.
4. 전략 정의 관리와 일반 사용자의 기존 전략 선택 책임이 분리되어 있지 않다.
5. 공유 설정 변경의 저장 시점과 trex-core 실행 슬롯의 실제 적용 시점이 구분되어 있지 않다.
### 승인된 범위
#### 읽기·선택 API
- 일반 사용자와 admin은 `GET /v1/strategies` 및 상세 조회 API로 active 전략을 조회할 수 있다.
- 일반 사용자는 자기 포트폴리오 생성·수정 시 기존 active `strategy_id`만 선택할 수 있다.
- 생성 시 `strategy_id`는 필수다.
- 전역 active 카탈로그가 선택 가능 여부의 권위이며, legacy `avabile_strategies`는 신규 API의 선택 권한 근거로 사용하지 않는다.
#### 관리자 카탈로그 API
- `GET /v1/admin/strategies`
- `POST /v1/admin/strategies`
- `PATCH /v1/admin/strategies/{strategy_id}`
- `DELETE /v1/admin/strategies/{strategy_id}`
- 생성·수정·삭제는 admin만 허용한다.
- admin 조회는 active와 inactive 전략을 모두 다룰 수 있다.
#### 공유 설정과 revision
- 전략은 안정적인 `strategy_id`로 식별되는 전역 공유 설정이다.
- admin PATCH는 기존 row의 `name/config`를 제자리 수정하고 동일 ID의 `revision`을 원자적으로 증가시킨다.
- 같은 ID를 참조하는 모든 포트폴리오의 이후 portfolio-hub API 조회에는 commit된 최신 config/revision이 반영된다.
- 실행 중 trex-core 슬롯에는 즉시 적용하지 않는다. trex-core가 재시작되거나 하루 한 번 정해진 시각에 슬롯을 갱신할 때 최신 revision을 가져와 그때부터 적용한다.
- 이미 생성된 슬롯·제출된 주문·완료된 거래에는 소급 적용하지 않는다.
- portfolio-hub는 downstream daemon을 강제로 재시작하거나 설정을 push하지 않는다.
#### 상태·삭제
- 전략에 `active/inactive` 상태를 둔다.
- 일반 조회와 신규 포트폴리오 선택에는 active 전략만 노출·허용한다.
- 기존 포트폴리오가 참조하는 전략을 inactive로 바꿔도 참조는 보존한다.
- DELETE는 어떤 포트폴리오에서도 참조하지 않는 전략만 hard delete한다.
- 참조 중인 전략 DELETE는 HTTP 409를 반환한다. 신규 선택을 막는 retirement는 PATCH inactive로 처리한다.
#### 포트폴리오 계약과 호환성
- `/v1/portfolios`와 legacy `/portfolios`의 생성·수정 모두 ID-only 선택으로 통일한다.
- 요청의 `strategy_name`·`strategy_params`는 제거하며 전달 시 HTTP 422로 거부한다.
- 응답의 `active_strategy_id`·`strategy_name`·`strategy_params`는 trex-core 및 전략 daemon 소비 계약이므로 유지한다.
- 비버전 `/strategy-sets` 경로는 새 `/v1` API 도입과 동시에 제거하며 deprecated alias를 유지하지 않는다.
- 운영자 CLI의 직접 저장소 쓰기는 신뢰된 운영자 전용 관리 경로로 유지하고 일반 사용자 API로 간주하지 않는다. HTTP와 동일한 상태·참조 무결성을 우회해서는 안 된다.
#### 기본 전략 migration
- Alembic data migration은 `name="target_value"`, `config={}`, `active`인 기본 전략을 idempotent하게 생성한다.
- 동일 `(name, config)` row가 이미 존재하면 해당 row를 재사용한다.
- 기본 전략이 존재하더라도 포트폴리오 생성 요청의 `strategy_id` 필수 조건은 완화하지 않는다. 호출자는 카탈로그 조회로 ID를 얻어 명시한다.
#### 오류 계약
- 미존재 `strategy_id`: HTTP 404
- inactive 전략 신규 선택: HTTP 409
- 제거된 `strategy_name`·`strategy_params` 또는 malformed 입력: HTTP 422
- 권한 부족: HTTP 403
- 참조 중 전략 DELETE: HTTP 409
### 승인된 결정 사항
1. 외부 API 명칭은 `/v1/strategies`, `/v1/admin/strategies`를 사용한다.
2. `active/inactive` 상태를 도입하고 inactive 참조는 보존하되 신규 선택은 금지한다.
3. PATCH는 기존 ID의 공유 설정을 제자리 수정하여 모든 참조 포트폴리오의 이후 API 조회에 반영한다.
4. 포트폴리오는 active `strategy_id`를 필수로 명시하고 전역 active 카탈로그를 선택 권위로 사용한다.
5. 포트폴리오 요청의 `strategy_name`·`strategy_params`는 제거하고 422로 거부한다.
6. 비버전 `/strategy-sets`는 새 API 도입과 동시에 제거한다.
7. 기본 `target_value`/`{}` 전략은 Alembic migration으로 idempotent하게 생성한다.
8. legacy `/portfolios`도 ID-only로 통일하고 CLI는 신뢰된 운영자 경로로 유지한다.
9. portfolio-hub 저장은 즉시 보이지만 실행 슬롯 적용은 trex-core 슬롯 재생성·정기 갱신 시점으로 지연한다.
10. 기본 전략은 active 상태로 생성하거나 기존 동일 row를 재사용한다.
11. 미존재 404, inactive 409, legacy/malformed 422, 권한 부족 403으로 구분한다.
12. 참조 중 전략은 삭제하지 않고 409를 반환하며 retirement는 inactive로 처리한다.
### 승인된 수용 기준
- [ ] 일반 사용자 토큰으로 active 전략 카탈로그 목록·상세를 조회할 수 있다.
- [ ] 일반 사용자 토큰으로 전략 설정 생성·수정·삭제 시 HTTP 403을 반환한다.
- [ ] admin 토큰으로 전략 설정 생성·조회·수정·상태 변경·미참조 삭제가 가능하다.
- [ ] admin이 공유 설정을 수정하면 ID는 유지되고 revision이 증가하며 모든 참조 포트폴리오의 이후 API 조회에 최신 설정이 나타난다.
- [ ] inactive 전략은 일반 카탈로그와 신규 선택에서 제외되지만 기존 포트폴리오 참조는 보존된다.
- [ ] 포트폴리오 생성 시 active `strategy_id`가 필수이며 수정 시에도 active ID만 허용한다.
- [ ] `/v1/portfolios`와 legacy `/portfolios`가 새로운 전략 설정을 간접 생성하지 않는다.
- [ ] 포트폴리오 요청의 `strategy_name`·`strategy_params`는 HTTP 422로 거부하지만 응답의 전략 필드는 유지된다.
- [ ] 미존재·inactive·권한 부족·malformed 요청이 승인된 HTTP 상태 코드로 구분된다.
- [ ] 새 API 도입 시 비버전 `/strategy-sets` 경로가 제거되고 관련 문서·테스트가 갱신된다.
- [ ] migration이 기본 active `target_value`/`{}` 전략을 중복 없이 생성 또는 재사용한다.
- [ ] 참조 중 전략 DELETE는 409이며 포트폴리오 참조를 자동 이전하거나 삭제하지 않는다.
- [ ] trex-core 슬롯은 설정 PATCH만으로 즉시 변경되지 않고 다음 슬롯 재생성·정기 갱신에서 최신 revision을 반영한다.
- [ ] 권한·간접 생성·상태·revision·오류·삭제·migration·호환성에 대한 회귀 테스트가 추가된다.
### 범위 밖
- trex-core의 정확한 일일 슬롯 갱신 시각 및 scheduler 구현 변경
- portfolio-hub가 downstream daemon을 강제로 reload·restart하거나 설정을 push하는 기능
- 이미 생성된 슬롯·제출된 주문·완료된 거래의 소급 재해석
- 내부 DB 테이블명 `strategy_sets` 변경
- 운영 배포·데이터 migration 실행·release 승인
### 위험 및 의존성
- `/strategy-sets` 즉시 제거는 breaking change다. 현재 워크스페이스에서 외부 코드 소비자는 발견되지 않았지만 문서와 운영 호출자는 배포 전에 새 경로로 전환해야 한다.
- 공유 config는 mutable하므로 revision 증가와 원자적 update가 필요하다.
- trex-core의 실제 반영은 슬롯 refresh 계약에 의존한다. #26은 최신 revision을 제공하는 portfolio-hub 경계까지만 소유한다.
- legacy 요청 필드가 framework 기본 동작으로 조용히 무시되지 않도록 명시적으로 422를 보장해야 한다.
### Approval State
- **Status:** APPROVED
- **Approved by:** genichin
- **Approved at:** 2026-08-14T08:14:31Z
- **Approval basis:** 이슈 #26의 OQ-001~OQ-012 단일 선택 완료와 요청자의 명시적 Discovery 승인 지시
- **Unresolved Open Questions:** 없음
- **Authority boundary:** 이 승인은 위 Discovery 범위를 승인하며 구현 완료, main 반영, 배포 또는 release 승인을 의미하지 않는다.
## Out of Scope
### 사후 ProofLine 경계
- 이 사후 사양 포착은 구현이 ProofLine 정식 시퀀스로 수행됐다는 주장이나 implementation/delivery/release/deployment lifecycle closure를 만들지 않는다.
- 구현 진행·완료 댓글은 canonical lifecycle artifact가 아니라 출처가 명시된 비정식 evidence다.
- 현재 구현은 미커밋 working tree에 있으므로 exact implementation commit, 원격 main 통합, push, PR, 게시, 운영 배포 및 release는 확인되거나 승인된 것으로 간주하지 않는다.
- 이슈 원문의 `범위 밖` 항목과 authority boundary는 위 승인 원문 전체에 보존되며 `req-0003` 및 연결 AC가 이를 축소하거나 확장하지 않는다.
@@ -0,0 +1,61 @@
# 구현 완료 — GitHub Issue #26 비정식 증적
> 이 문서는 ProofLine canonical lifecycle artifact가 아니다. #26 구현 당시 GitHub 댓글을 출처와 함께 원문 보존한 비정식 증적이다.
> 이 증거는 로컬 구현·검증 상태를 기록하지만 ProofLine 정식 implementation chronology, delivery closure, push, PR, 원격 main 반영, release 또는 deployment 완료를 소급 증명하지 않는다.
> Source: https://github.com/fourblocks2020/portfolio-hub/issues/26#issuecomment-5291927258
> Comment ID: `IC_kwDOR4l8PM8AAAABO2xm2g`
> Author: `genichin`
> Created: `2026-08-14T09:48:42Z`
> Original body SHA-256: `c3bc9a3ad6c9cc6678ed700a11add03b1f5abe71645a21e157dc091faec28f10`
> Original body boundary: 아래 `---` 다음 byte부터 파일 끝까지가 GitHub 원문 body다.
---
## 구현 완료
이슈 #26 **전략 API v1 이전 및 관리자 전역 카탈로그 권한 분리**의 승인 범위를 로컬 `main` 작업 트리에 구현하고 실제 PostgreSQL 검증 및 독립 QC까지 완료했습니다.
### 구현 내용
- 비버전 `/strategy-sets` 제거 및 전략 API `/v1` 이전
- 일반 사용자: active 전략 조회 전용 `/v1/strategies`
- 관리자: 전체 전략 카탈로그 CRUD `/v1/admin/strategies`
- 전략 카탈로그에 `active/inactive` 상태와 revision 도입
- 신규 전략 revision 1, 실질적 PATCH만 revision 증가, no-op 유지
- 동시 PATCH를 `FOR UPDATE`로 직렬화하고 최신 name/config 기준으로 derived `nick` 계산
- 포트폴리오 create/update에서 기존 active `strategy_id`만 선택
- `strategy_name`·`strategy_params`를 통한 간접 전략 생성 차단
- 전략 선택의 `FOR SHARE`와 포트폴리오 저장을 같은 트랜잭션으로 묶어 inactivate/delete race 차단
- 전략 DELETE의 `FOR UPDATE`·두 참조 형태 검사·삭제를 한 트랜잭션으로 통합
- stale BUY/SELL snapshot이 전략 선택 컬럼을 덮어쓰지 못하도록 거래 저장 책임 분리
- 거래 portfolio UPDATE와 ledger INSERT 원자화
- 포트폴리오 응답에 최신 `strategy_revision` 노출
- 기본 `target_value`/`{}` 전략 migration과 runtime nick 파생 규칙 통일
- CLI, 문서, migration 및 동시성 회귀 테스트 갱신
### 실제 검증 결과
- 이슈 집중 회귀: `99 passed`
- 실제 PostgreSQL 동시성·migration 통합 테스트: 최종 `4 passed`
- 동시 partial PATCH의 nick/revision 일관성
- 전략 선택 대 DELETE race의 orphan 방지
- stale A snapshot → B 선택 → A 삭제 → stale BUY 저장 후 B 유지
- 신규 DB 전체 Alembic upgrade 및 head 재실행
- 관련 최종 회귀: `75 passed`
- 알려진 기존 문제 파일 제외 광범위 회귀: `608 passed, 3 skipped`
- 독립 최종 QC 재실행: PostgreSQL `4 passed`, focused `37 passed, 4 skipped`
- Python compile, `git diff --check`, 변경 핵심 파일 Ruff 검사: 통과
- 독립 QC 최종 판정: **PASS**
전체 broad 실행에 남은 3건은 저장소 밖 `temp/deleted_20260813/.worktrees`를 스캔하는 기존 환경 오염 실패로, 이번 변경에 의한 회귀가 아닙니다.
### 비차단 후속 권고
- PostgreSQL 통합 테스트 cleanup을 fixture/`finally` 기반으로 강화하면 테스트가 중간 실패·중단될 때의 잔여 fixture 방지를 개선할 수 있습니다. 현재 성공 경로 cleanup은 QC 전후 잔여 레코드 수가 동일해 정상입니다.
### 완료 경계
- 완료: 로컬 구현, 실제 테스트, migration 검증, 문서 갱신, 독립 QC
- 미수행: 커밋, push, PR, 원격 `main` 반영, 이미지 게시, 운영 설정 변경, 배포, 릴리스
현재 변경은 기준 HEAD `5fc389dea8f05c6f711ecfe874f055979af5e58d`의 로컬 `main` 작업 트리에 **미커밋 상태**로 보존되어 있습니다. 이 댓글은 제품 구현·검증 완료를 뜻하며 배포·릴리스 또는 이슈 lifecycle closure를 뜻하지 않습니다.
@@ -0,0 +1,65 @@
# 구현 진행 상황 — GitHub Issue #26 비정식 증적
> 이 문서는 ProofLine canonical lifecycle artifact가 아니다. #26 구현 당시 GitHub 댓글을 출처와 함께 원문 보존한 비정식 증적이다.
> 이 증거는 로컬 구현·검증 상태를 기록하지만 ProofLine 정식 implementation chronology, delivery closure, push, PR, 원격 main 반영, release 또는 deployment 완료를 소급 증명하지 않는다.
> Source: https://github.com/fourblocks2020/portfolio-hub/issues/26#issuecomment-5291151212
> Comment ID: `IC_kwDOR4l8PM8AAAABO2CPbA`
> Author: `genichin`
> Created: `2026-08-14T08:21:45Z`
> Original body SHA-256: `539cfe82e6f8281c5821c52f484a4078b116e52f5696ee0f9ea502f27b42cfeb`
> Original body boundary: 아래 `---` 다음 byte부터 파일 끝까지가 GitHub 원문 body다.
---
## 구현 진행 상황
- **대상:** 전략 API v1 이전 및 관리자 전역 카탈로그 권한 분리
- **브랜치:** `main`
- **기준:** `main` (`5fc389dea8f05c6f711ecfe874f055979af5e58d`)
- **상태:** 로컬 구현·검증·독립 QC 완료 (PASS)
- **최종 업데이트:** 2026-08-14 18:47 KST
### 구현 계획 및 작업 목록
- [x] 전략 카탈로그 데이터 모델과 저장소 기능 확장
- 안정적인 전략 ID를 유지하면서 `active/inactive` 상태와 원자적으로 증가하는 revision을 저장·조회하고, 동시 PATCH에서도 `name/config/nick` 일관성을 보장한다.
- [x] `/v1/strategies` 일반 조회 API 구현
- 일반 사용자는 active 전략의 목록과 상세만 조회할 수 있도록 한다.
- [x] `/v1/admin/strategies` 관리자 관리 API 구현
- admin만 전체 목록 조회, 생성, 제자리 수정·상태 변경, 미참조 전략 삭제를 수행할 수 있도록 한다.
- [x] 포트폴리오 전략 선택을 기존 active `strategy_id` 전용으로 변경
- `/v1/portfolios`와 legacy `/portfolios` 모두 생성 시 전략 ID를 필수로 받고, 수정 시에도 기존 active 전략만 선택하며 선택·저장·삭제 race를 DB 트랜잭션과 row lock으로 차단한다.
- [x] 동시성·참조 무결성 회귀 검증
- 동시 전략 PATCH와 포트폴리오 선택·전략 삭제 경합에서도 derived `nick`, revision, active 선택, orphan 방지가 유지되는지 실제 PostgreSQL로 검증한다.
- [x] 일반 사용자의 간접 전략 생성 및 비버전 API 제거
- 포트폴리오 요청의 `strategy_name`·`strategy_params`를 422로 거부하고 비버전 `/strategy-sets` 라우트를 제거한다.
- [x] 기본 전략 데이터 migration과 운영자 CLI 무결성 반영
- 기본 active `target_value`/`{}` 전략을 중복 없이 생성·재사용하고, CLI가 상태·revision·참조 무결성을 우회하지 않도록 한다.
- [x] 권한·상태·revision·오류·삭제·호환성 회귀 테스트 추가 및 실행
- 승인된 HTTP 403/404/409/422 계약, 참조 보존, 간접 생성 차단과 migration idempotency를 실제 테스트로 검증한다.
- [x] 인터페이스·런타임·데이터 모델 문서 갱신 및 전체 변경 검수
- 새 API 계약과 비버전 경로 제거, downstream 적용 시점의 경계를 문서화하고 전체 diff와 회귀를 점검한다.
### 진행 기록
- 2026-08-14 17:21 KST — 이슈 #26의 승인된 범위와 수용 기준을 기준으로 구현 계획을 확정했다.
- 2026-08-14 17:21 KST — 별도 구현 브랜치 없이 `main` (`5fc389d`)에서 구현·검증을 시작했다.
- 2026-08-14 17:32 KST — v1 전략 API, 전략 상태·revision 저장소, ID 전용 포트폴리오 선택, 비버전 라우트 제거 및 migration의 초기 구현을 작업트리에 반영했다. 아직 검증 전이므로 체크하지 않았다.
- 2026-08-14 17:33 KST — 첫 전체 회귀 실행 결과 `571 passed, 27 failed, 6 skipped`였다. 새 필수 `strategy_id` 계약에 맞지 않는 기존 테스트·CLI와 미완료 전용 테스트·문서를 보완한 뒤 다시 검증한다.
- 2026-08-14 17:44 KST — CLI·기존 테스트·전용 API/migration 테스트·문서를 보완했다. 이슈 집중 회귀는 `166 passed`, 전체 제품 테스트는 기존 stale worktree 정적검사 3건을 제외하고 `606 passed, 3 skipped`였다.
- 2026-08-14 17:49 KST — 실제 PostgreSQL 신규 DB에서 Alembic 전체 upgrade와 head 재실행을 검증했다. 기본 `target_value`/`{}` 전략은 active, revision 1로 한 건 생성됐다.
- 2026-08-14 17:50 KST — 실제 PostgreSQL 기반 HTTP 흐름에서 admin 생성·PATCH·삭제, 일반 조회·권한 거부, ID 유지와 revision 증가를 검증했다.
- 2026-08-14 17:51 KST — 검증 중 발견한 inactive 신규 전략이 revision 2로 시작하는 결함을 수정해 실제 DB와 회귀 테스트에서 revision 1을 확인했다.
- 2026-08-14 17:52 KST — 독립 QC가 동시 PATCH의 derived `nick` 불일치 가능성과 전략 선택·삭제 사이의 참조 race를 HIGH로 판정해 반려했다. 특히 참조 race 수정은 포트폴리오 mutation과 전략 삭제를 같은 DB 트랜잭션/row lock 경계로 묶는 작업이 추가로 필요해, 승인된 구현 계획 보완이 필요하므로 구현을 중지했다.
- 2026-08-14 18:17 KST — 요청자가 트랜잭션·row lock·동시성 테스트를 구현 계획에 추가하고 QC 지적 전체를 수정하도록 명시 승인해 구현을 재개했다.
- 2026-08-14 18:34 KST — 전략 PATCH를 `FOR UPDATE` 트랜잭션으로 직렬화하고, active 전략 선택의 `FOR SHARE` lock과 포트폴리오 INSERT/UPDATE를 같은 트랜잭션으로 묶었다. 전략 DELETE도 `FOR UPDATE`·참조 검사·삭제를 한 트랜잭션으로 통합했다.
- 2026-08-14 18:34 KST — 포트폴리오 응답에 최신 `strategy_revision`을 추가하고, migration 기본 전략 nick을 runtime SHA-1 규칙과 일치시켰다.
- 2026-08-14 18:34 KST — 집중 회귀 `99 passed`, 실제 PostgreSQL 동시 PATCH·선택/삭제 race·신규 DB migration 재실행 통합 테스트 `3 passed`, 알려진 외부 정적 sweep 및 기존 ledger fixture 파일을 제외한 광범위 회귀 `607 passed, 3 skipped`를 확인했다. Python compile, `git diff --check`, 변경 핵심 파일 Ruff 검사도 통과했다.
- 2026-08-14 18:34 KST — 전체 원문 실행은 `609 passed, 3 failed, 3 errors, 3 skipped`였다. 실패 3건은 저장소 밖 `temp/deleted_20260813/.worktrees`를 스캔하는 기존 정적 테스트이고, error 3건은 기존 ledger integration fixture가 현재 DB의 필수 `owner_user_id`를 넣지 않는 문제로 이번 전략 변경과 분리했다. 독립 재-QC를 요청했다.
- 2026-08-14 18:40 KST — 독립 재-QC는 거래 저장 `save()`가 stale 포트폴리오 snapshot의 전략 A를 다시 써, B로 선택 변경 후 삭제된 A 참조를 되살릴 수 있음을 HIGH로 재현해 FAIL 판정했다. 이전 findings는 모두 해소된 것으로 확인됐다.
- 2026-08-14 18:43 KST — `save()`에서 전략 선택 컬럼 쓰기를 제거하고 balance/price/buffer/timestamp UPDATE와 ledger INSERT를 단일 트랜잭션으로 묶었다. A snapshot→B 선택→A 삭제→stale BUY 저장 후에도 B가 유지되고 A가 없는 실제 PostgreSQL 회귀 테스트를 추가했다.
- 2026-08-14 18:43 KST — 실제 PostgreSQL 통합 테스트 `4 passed`, 관련 회귀 `75 passed`, 알려진 기존 문제 파일 제외 광범위 회귀 `608 passed, 3 skipped`, 핵심 Ruff·compile·diff 검사를 통과해 최종 독립 재-QC를 요청했다.
- 2026-08-14 18:47 KST — 최종 독립 재-QC가 PASS를 판정했다. stale BUY/SELL snapshot의 삭제 전략 참조 복원 문제가 완전히 해소됐고, 거래 UPDATE와 ledger INSERT의 단일 트랜잭션, selection/strategy lock 상호작용, 실제 PostgreSQL 회귀 시나리오를 확인했다. 독립 재실행은 PostgreSQL `4 passed`, focused `37 passed, 4 skipped`, compile·diff 검사 통과였다.
- 2026-08-14 18:47 KST — 비차단 테스트 위생 권고: PostgreSQL 통합 테스트 cleanup을 향후 fixture/finally로 강화하면 테스트 중단 시 잔여 fixture를 더 확실히 방지할 수 있다. 현재 성공 경로 cleanup은 전후 잔여 레코드 수가 동일해 정상이다.
> 모든 체크는 로컬 작업 트리의 구현·테스트·독립 QC 완료를 뜻한다. 커밋·push·PR·main 원격 반영·게시·배포·릴리스는 수행하지 않았으며 별도 지시와 게이트가 필요하다.
+3
View File
@@ -0,0 +1,3 @@
---
id: "line-0003"
---
+65
View File
@@ -0,0 +1,65 @@
---
id: "req-0003"
status: approved
discovery: "dcy-0003"
criteria:
create:
- "ac-0013"
- "ac-0014"
- "ac-0015"
- "ac-0016"
- "ac-0017"
- "ac-0018"
- "ac-0019"
- "ac-0020"
- "ac-0021"
- "ac-0022"
- "ac-0023"
update: []
retire: []
satisfy: []
---
# 전략 API v1 이전 및 관리자 전역 카탈로그 권한 분리
## Objective
전역 전략 카탈로그의 일반 조회·선택과 관리자 관리를 versioned API에서 분리하고, 안정적인 전략 identity·revision·상태·참조 무결성을 유지하면서 포트폴리오의 ID-only 선택 계약과 downstream 적용 시점 경계를 제공한다.
## Scope
- 일반 사용자와 admin의 active 전략 목록·상세 조회를 `/v1/strategies`로 제공한다.
- active·inactive 전체 전략의 생성·조회·제자리 수정·상태 변경·미참조 삭제를 `/v1/admin/strategies`의 admin 전용 권한으로 제공한다.
- 전략의 안정적인 ID, 양의 revision, active/inactive 상태와 동시 변경 시 원자적 일관성을 보장한다.
- `/v1/portfolios`와 legacy `/portfolios`의 생성·수정을 기존 active `strategy_id` 전용으로 통일하고 간접 전략 생성을 제거한다.
- 요청의 제거된 전략 이름·파라미터 필드는 거부하되 기존 downstream 응답 필드는 최신 공유 설정과 revision으로 유지한다.
- 참조 중 전략의 hard delete를 거부하고 inactive retirement 및 기존 참조 보존을 지원한다.
- 비버전 `/strategy-sets`를 alias 없이 제거하고 운영자 CLI가 HTTP와 같은 상태·참조 무결성을 우회하지 않게 한다.
- 기본 active `target_value`/`{}` 전략을 idempotent Alembic data migration으로 생성하거나 기존 동일 row를 재사용한다.
- portfolio-hub의 commit된 최신 revision 제공 시점과 trex-core 슬롯의 재생성·정기 갱신 적용 시점을 구분한다.
- 동시 PATCH, 선택·상태 변경·삭제 경합 및 stale 거래 snapshot 저장에서도 derived field, revision, 현재 참조와 ledger 원자성을 보존한다.
## Constraints
- 전역 active 카탈로그가 신규 선택 권위이며 legacy `avabile_strategies`는 선택 권한 근거가 아니다.
- 외부 오류 계약은 미존재 `404`, inactive 신규 선택과 참조 중 삭제 `409`, 제거 필드·malformed 입력 `422`, 권한 부족 `403`으로 구분한다.
- 기존 inactive 전략 참조는 보존하며 자동 이전하거나 삭제하지 않는다.
- portfolio-hub는 downstream daemon을 강제 reload/restart하거나 설정을 push하지 않고 이미 생성된 슬롯·주문·완료 거래를 소급 재해석하지 않는다.
- 내부 DB 테이블명 `strategy_sets`는 유지한다.
### Current Implementation Assessment
- 이 REQ는 요청자 지시에 따라 사후 승인 상태로 포착됐다. `approved`/`active`는 normative authority이며 ProofLine 구현 chronology나 satisfaction marker가 아니다.
- 현재 `main` 기준 HEAD `5fc389dea8f05c6f711ecfe874f055979af5e58d` 위 미커밋 working tree에 portfolio-hub 범위의 구현·테스트·문서 변경이 존재한다.
- 2026-08-15 현재 scoped 재실행 `uv run python -m pytest -q tests/test_v1_strategies.py tests/test_strategy_catalog_migration.py tests/test_v1_portfolios_crud.py tests/test_strategy_catalog_postgres_integration.py tests/test_cli.py`는 `70 passed, 4 skipped`였다. 이어 `PORTFOLIOHUB_RUN_POSTGRES_INTEGRATION=1 uv run python -m pytest -q tests/test_strategy_catalog_postgres_integration.py`를 실행해 실제 PostgreSQL integration `4 passed`를 확인했다.
- `ac-0013`~`ac-0021`, `ac-0023`은 현재 working-tree 구현과 executable tests가 대응한다. 다만 `ac-0014`의 비-admin mutation별 독립 negative test, `ac-0017`의 제거 요청 필드·응답 호환 필드 전용 test, `ac-0023`의 ledger INSERT 실패 fault-injection rollback test는 `PARTIAL`이며, exact implementation commit과 ProofLine delivery closure도 없다.
- `ac-0022`의 portfolio-hub 경계(최신 revision 조회 제공, 강제 push/reload 부재)는 구현되어 있으나, trex-core가 다음 슬롯 재생성·정기 갱신에서 최신 revision을 실제 반영한다는 cross-repository end-to-end seam은 이 저장소 증거만으로 확인되지 않아 `UNVERIFIED`다.
## Non-Goals
- trex-core의 정확한 일일 슬롯 갱신 시각 또는 scheduler 구현 변경
- downstream daemon 강제 reload·restart 또는 설정 push
- 기존 슬롯·주문·완료 거래의 소급 재해석
- 내부 `strategy_sets` 테이블명 변경
- 운영 배포, production data migration 실행, push, PR, 원격 main 반영 또는 release 승인
- 이 사후 문서로 과거 구현에 ProofLine 정식 implementation/delivery chronology나 terminal closure를 소급 부여하는 행위