diff --git a/.gitignore b/.gitignore index b6d0a84..d3cadee 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ htmlcov/ Thumbs.db # Agent worktree 격리 디렉토리 (커밋 금지) .claude/worktrees/ +/.worktrees/ diff --git a/.proofline/identities.json b/.proofline/identities.json index bae8b70..92a1f36 100644 --- a/.proofline/identities.json +++ b/.proofline/identities.json @@ -1,5 +1,5 @@ { "schema_version": 1, - "next_line_number": 4, + "next_line_number": 5, "next_ac_number": 24 } diff --git a/.proofline/lines/line-0004/dcy-0004.md b/.proofline/lines/line-0004/dcy-0004.md new file mode 100644 index 0000000..9281152 --- /dev/null +++ b/.proofline/lines/line-0004/dcy-0004.md @@ -0,0 +1,169 @@ +--- +id: "dcy-0004" +status: confirmed +--- + +# 포트폴리오 생성 시 연결 데몬 persistence 및 배정 검증 + +## Problem + +포트폴리오 생성 요청에 선택한 `managed_by_daemon_id`가 포함되어도 `portfolio-hub`의 `POST /v1/portfolios` handler와 repository create port 사이에서 해당 값이 누락되어 persistence되지 않는다. 비-null PATCH 재지정은 전달되므로 사용자는 생성 직후 다시 편집해야 연결 데몬을 반영할 수 있다. + +이 Discovery는 요청자가 승인한 GitHub 이슈 #27을 canonical ProofLine 문서로 포착한다. 아래 `Scope`의 **승인된 이슈 본문 전체**는 구조적 heading만 canonical H2 충돌을 피하도록 한 단계 낮췄으며, 원문의 비어 있지 않은 모든 내용과 승인 경계를 순서대로 보존한다. + +## Evidence + +### 승인 원문과 provenance + +- 원문: https://github.com/fourblocks2020/portfolio-hub/issues/27 +- 원문 제목: `trex-web : 포트폴리오 추가시 연결 데몬을 설정하였음에도 지정되지 않음` +- 원문 상태: `open` +- 원문 최초 작성: `2026-07-21T14:42:15Z` +- 승인 본문 최종 갱신: `2026-08-15T03:03:53Z` +- 원문 body SHA-256: `17668fbfac2419713e8247637fbf05631d93a71bdb74db0fe122743fba8d44e4` +- 원문 승인 상태: `APPROVED`, 승인자 `genichin`, 승인 시각 `2026-08-15T03:02:35Z`, unresolved Open Questions 없음. +- 이슈는 최초 `fourblocks2020/trex-web#10`에서 생성됐으나 근본 원인이 `portfolio-hub`의 create persistence 경계로 확인되어 현재 `fourblocks2020/portfolio-hub#27`로 이전됐다. +- exact 승인 본문 snapshot은 `evidence/issue-27-approved-discovery.md`에 보존한다. +- source review 기준 commit은 `portfolio-hub` `2a1a637fb9fb19944991cb902530faf72f0e9f84`이며, 이 문서 작성 시 product 구현은 수행하지 않았다. + +### 승인·handoff 경계 + +- 요청자는 OQ-001~OQ-004의 단일 선택을 완료하고 Discovery 검토·승인을 명시했다. +- `trex-web` 관련 코드·타입·화면·테스트 수정은 명시적으로 Out of Scope다. +- 이 confirmed Discovery는 다음 REQ 작성 및 검토를 위한 scope authority다. 구현 완료, approved REQ, active/satisfied AC, implementation/delivery closure, main 반영, push, PR, migration 실행, 배포 또는 release 완료를 주장하지 않는다. + +## Scope + +### 승인된 이슈 본문 전체 + +### 원래 제보 + +> 편집으로 다시 지정해야 반영 됨. + +### ProofLine Discovery + +- **Status:** APPROVED +- **Source:** 프로젝트 보드 `trex` / 현재 `fourblocks2020/portfolio-hub#27` (최초 `fourblocks2020/trex-web#10`에서 이전) +- **검토 기준:** `portfolio-hub` `2a1a637fb9fb19944991cb902530faf72f0e9f84`, 증상 경계 확인용 `trex-web` `b72ccd7cd639447e71d7e785857d2c503e3a1841` +- **승인 경계:** 이 승인은 아래 `portfolio-hub` Discovery 범위를 승인한다. 구현 완료, `trex-web` 변경, main 반영, push, PR, 배포 또는 release 승인을 의미하지 않는다. + +#### Problem + +포트폴리오 추가 화면에서 사용자가 `연결 데몬`을 선택해 생성해도 생성된 포트폴리오의 `managed_by_daemon_id`가 저장되지 않는다. 같은 포트폴리오를 편집하여 비-null 데몬을 다시 지정하면 반영된다. + +프런트엔드는 생성 요청에 선택값을 포함하지만, `portfolio-hub`의 `POST /v1/portfolios` 처리기가 해당 값을 저장소 생성 호출로 전달하지 않는다. 반면 `PATCH /v1/portfolios/{portfolio_id}`는 비-null `managed_by_daemon_id`를 update fields에 포함하므로 “생성에서는 누락되고 편집에서는 반영”되는 제보와 코드 경로가 일치한다. + +#### Evidence + +- Live issue: https://github.com/fourblocks2020/portfolio-hub/issues/27 + - 원래 제보: “편집으로 다시 지정해야 반영 됨.” + - 최초 작성: `2026-07-21T14:42:15Z` +- `trex-web/features/portfolio/portfolio-create-form.tsx:83-91` + - 선택한 데몬을 `managed_by_daemon_id: selectedDaemonId || null`로 생성 mutation에 전달한다. +- `trex-web/lib/api/portfolio-hub.ts:87-95,251-256` + - 생성 요청 타입과 `POST /v1/portfolios` wrapper가 `managed_by_daemon_id`를 허용하고 payload를 그대로 전송한다. +- `portfolio-hub/app/schemas/v1.py:235-258` + - 생성·수정 schema 모두 `managed_by_daemon_id` 입력을 허용한다. +- `portfolio-hub/app/api/v1_portfolios.py:170-183` + - 생성 handler가 `repo.create_portfolio(...)`에 `managed_by_daemon_id`를 전달하지 않는다. +- `portfolio-hub/app/repositories/portfolio_repository.py:150-165` + - 현재 생성 port 시그니처에도 `managed_by_daemon_id`가 없다. +- `portfolio-hub/app/api/v1_portfolios.py:222-235` + - 편집 handler는 `exclude_none=True`로 얻은 비-null fields를 `repo.update_portfolio(...)`에 전달하므로 비-null 데몬 재지정은 저장된다. +- `portfolio-hub/app/api/v1_daemons.py:26-56` + - 일반 daemon 목록은 호출자 소유 범위이며 active와 inactive를 `is_active`로 구분해 반환한다. +- `portfolio-hub/docs/data-model.md:231-259` + - daemon ID는 server-generated UUID이고, daemon은 사용자 소유이며, service token 바인딩에는 존재·active·동일 소유자 규칙이 적용된다. 비활성화는 토큰을 revoke하지만 기존 portfolio reference 자동 제거는 규정하지 않는다. +- `portfolio-hub/docs/interface-contract.md:301-315` + - 포트폴리오 생성·수정 API가 `managed_by_daemon_id`를 입력으로 공개하며 portfolio 소유권과 오류 계약의 기존 경계를 정의한다. + +#### 승인된 결정 + +1. 이 이슈는 `portfolio-hub`가 소유하는 repo-local 수정만 다룬다. `trex-web` 코드·테스트와 필수 `strategy_id` UI 적응은 이 이슈의 범위 밖이다. +2. 포트폴리오 생성·수정에서 비-null daemon을 지정하려면 지정 시점에 daemon이 존재하고 active이며 포트폴리오 소유자와 같은 사용자가 소유해야 한다. +3. admin이 다른 사용자의 포트폴리오를 생성·수정할 때도 daemon 소유자는 호출 admin이 아니라 대상 포트폴리오 소유자와 일치해야 한다. +4. 미존재 또는 배정 권한이 없는 foreign daemon은 존재를 숨겨 HTTP 404, 소유 범위 안의 inactive daemon은 HTTP 409, UUID 형식이 아닌 daemon ID는 HTTP 422로 반환한다. +5. `PATCH`의 명시적 `managed_by_daemon_id: null` 해제 결함은 별도 이슈로 추적하고 이번 범위에 포함하지 않는다. + +#### Scope + +- `POST /v1/portfolios`가 요청의 비-null `managed_by_daemon_id`를 생성 persistence까지 전달한다. +- 생성과 PATCH의 비-null daemon 지정·재지정은 같은 validation 계약을 사용한다. +- 요청 daemon ID는 daemon의 server-generated UUID 형식이어야 한다. +- daemon은 portfolio persistence 시점에 존재하고 active여야 하며 `daemon.owner_user_id == portfolio.owner_user_id`여야 한다. +- admin의 `user_id` override 생성에서는 최종 결정된 target `owner_user_id`를 daemon 소유권 비교 기준으로 사용한다. +- 미존재·foreign daemon은 HTTP 404, inactive daemon은 HTTP 409, malformed daemon ID는 HTTP 422로 실패한다. +- validation 실패 시 portfolio 생성·수정과 관련 부수 상태가 부분 반영되지 않는다. +- daemon validation과 portfolio write는 동시 변경에 대해 하나의 원자적 판정 경계를 가져야 한다. assignment가 먼저 확정된 뒤 daemon이 비활성화되면 기존 deactivation 계약에 따라 service token은 revoke되고 portfolio reference는 보존된다. 비활성화가 먼저 확정되면 새 assignment는 HTTP 409로 실패한다. +- 데몬을 선택하지 않은 생성 요청은 `managed_by_daemon_id = null`인 unassigned portfolio를 생성하는 기존 동작을 유지한다. +- 성공한 생성 응답과 후속 portfolio 조회는 같은 `managed_by_daemon_id`를 반환한다. +- 기존 비-null PATCH 지정·재지정 경로에는 위 validation을 적용하되, 포트폴리오 소유권·disabled 보호·service/client 거부 등 기존 mutation 계약을 유지한다. +- `portfolio-hub` 단위/API 테스트와 disposable PostgreSQL 통합 테스트로 성공·미선택·malformed·미존재·foreign·inactive·admin target-owner·동시성·`POST → GET` persistence를 검증한다. + +#### 승인된 수용 기준 후보 + +- [ ] 소유자의 active daemon을 선택해 포트폴리오를 생성하면 HTTP 201 응답의 `managed_by_daemon_id`가 선택한 ID와 같다. +- [ ] 같은 포트폴리오를 후속 조회해도 선택한 daemon ID가 보존된다. +- [ ] daemon 미선택 생성은 `managed_by_daemon_id = null`을 유지한다. +- [ ] UUID 형식이 아닌 daemon ID는 HTTP 422이며 portfolio가 생성·수정되지 않는다. +- [ ] 미존재 또는 portfolio 소유자와 다른 사용자의 daemon ID는 HTTP 404이며 portfolio가 생성·수정되지 않는다. +- [ ] portfolio 소유자 범위의 inactive daemon ID는 HTTP 409이며 portfolio가 생성·수정되지 않는다. +- [ ] admin target-user 생성·수정은 target portfolio owner 소유의 active daemon만 허용한다. +- [ ] 비-null PATCH 지정·재지정도 생성과 동일한 daemon validation과 오류 계약을 적용한다. +- [ ] assignment와 daemon 비활성화가 경합해도 inactive daemon에 새로운 assignment가 확정되는 중간 상태를 허용하지 않는다. +- [ ] assignment 확정 후 daemon 비활성화는 기존 portfolio reference를 자동 제거하지 않고 기존 token-revocation 계약을 유지한다. +- [ ] 실제 PostgreSQL persistence 경계에서 `POST → GET` 보존과 실패 시 무변경이 검증된다. +- [ ] 기존 portfolio 생성·수정의 인증, 소유권, strategy, asset, disabled-state 계약이 회귀하지 않는다. + +#### Out of Scope + +- `trex-web` 코드, 타입, 화면 또는 테스트 변경 +- `trex-web`의 `/v1/strategies` 조회·선택과 필수 `strategy_id` 계약 적응 +- `PATCH /v1/portfolios/{id}`의 명시적 `managed_by_daemon_id: null` 해제 지원 +- 데몬 실행·중지·재시작 또는 배포 자동화 +- 데몬 credential 관리 정책 변경 +- 포트폴리오의 자동 데몬 배정 +- daemon 비활성화 시 기존 portfolio reference 자동 해제 또는 다른 daemon으로 자동 이전 +- 기존 데이터 전체에 대한 일괄 backfill +- 내부 `daemons` 또는 `portfolios` 식별자·테이블 구조 변경 +- 이 Discovery 승인만으로 구현, main 반영, push, PR, migration 실행, 배포 또는 release 수행 + +#### Risks and Dependencies + +- `managed_by_daemon_id`는 DB-level FK가 없는 soft reference이므로 validation과 write를 분리하면 daemon 상태·소유권 변경과의 TOCTOU 위험이 생긴다. persistence transaction과 적절한 row-lock/동등한 직렬화 경계가 필요하다. +- daemon 소유자 재할당 기능이 있으므로 portfolio assignment와 동시 실행될 때도 최종 관계가 동일 소유자 규칙을 위반하지 않아야 한다. +- `trex-web`의 필수 `strategy_id` 계약 드리프트는 별도 범위다. 이번 backend 동작은 유효한 `strategy_id`를 포함한 직접 API 요청으로 독립 검증한다. +- 명시적 null 해제는 알려진 별도 결함으로 남으며 이번 승인 결과를 그 기능의 완료로 해석하지 않는다. + +### Approval State + +- **Status:** APPROVED +- **Approved by:** genichin +- **Approved at:** 2026-08-15T03:02:35Z +- **Approval basis:** OQ-001=A, OQ-002=A, OQ-003=A, OQ-004=A의 단일 선택과 `trex-web` 관련 수정·테스트를 Out of Scope로 두라는 요청자의 명시적 지시 +- **Unresolved Open Questions:** 없음 +- **Confirmation:** Discovery 검토·승인 완료 +- **Authority boundary:** 위 `portfolio-hub` Discovery 범위만 승인하며 구현 완료, lifecycle closure, main 반영, push, PR, 배포 또는 release를 의미하지 않는다. + +## Out of Scope + +### Canonical 경계 + +- 위 승인 원문 전체의 `Out of Scope`와 `Authority boundary`를 그대로 적용한다. +- 특히 `trex-web` 코드·타입·화면·테스트, `/v1/strategies` UI 적응, PATCH의 명시적 `managed_by_daemon_id: null` 해제, 자동 배정·이전·해제, backfill, 배포·release는 이 Line의 Discovery 범위가 아니다. +- 이 단계에서는 REQ·AC를 생성하거나 승인하지 않으며 product source, test, runtime configuration 또는 schema를 변경하지 않는다. + +## Risks and Unknowns + +### Risks + +- `managed_by_daemon_id`가 DB-level FK 없는 soft reference이므로 validation과 write가 분리되면 daemon 비활성화·소유자 변경과 TOCTOU가 발생할 수 있다. +- daemon 소유자 재할당과 portfolio assignment가 경합해도 최종 관계가 동일 소유자 규칙을 위반하지 않는 원자적 판정 경계가 필요하다. +- `trex-web`의 필수 `strategy_id` 계약 드리프트는 별도 범위이므로 이번 backend 검증은 유효한 `strategy_id`를 포함한 직접 API 요청으로 독립 수행해야 한다. +- 명시적 null 해제 결함은 알려진 별도 문제로 남으며 이번 Line 완료로 해석하지 않는다. + +### Assumptions + +- daemon ID의 canonical 형식은 기존 data model의 server-generated UUID다. +- daemon 비활성화가 assignment 확정 뒤 발생하면 기존 reference는 보존하고 service token revoke 계약이 적용된다. +- 위 가정은 승인된 이슈 본문과 현재 interface/data-model contract에서 직접 유도되며 추가 Open Question은 없다. diff --git a/.proofline/lines/line-0004/evidence/issue-27-approved-discovery.md b/.proofline/lines/line-0004/evidence/issue-27-approved-discovery.md new file mode 100644 index 0000000..5fe82f1 --- /dev/null +++ b/.proofline/lines/line-0004/evidence/issue-27-approved-discovery.md @@ -0,0 +1,123 @@ +# GitHub Issue #27 — 승인된 Discovery 원문 증거 + +- Source: https://github.com/fourblocks2020/portfolio-hub/issues/27 +- Repository: `fourblocks2020/portfolio-hub` +- Issue: `#27` +- Title: `trex-web : 포트폴리오 추가시 연결 데몬을 설정하였음에도 지정되지 않음` +- State: `open` +- Created at: `2026-07-21T14:42:15Z` +- Updated at: `2026-08-15T03:03:53Z` +- Body SHA-256: `17668fbfac2419713e8247637fbf05631d93a71bdb74db0fe122743fba8d44e4` +- Captured for: `line-0004` / `dcy-0004` +- Evidence class: 외부 승인 원문의 immutable-by-hash snapshot; canonical lifecycle state는 `dcy-0004.md` frontmatter가 소유한다. + +## Exact approved issue body + +## 원래 제보 + +> 편집으로 다시 지정해야 반영 됨. + +## ProofLine Discovery + +- **Status:** APPROVED +- **Source:** 프로젝트 보드 `trex` / 현재 `fourblocks2020/portfolio-hub#27` (최초 `fourblocks2020/trex-web#10`에서 이전) +- **검토 기준:** `portfolio-hub` `2a1a637fb9fb19944991cb902530faf72f0e9f84`, 증상 경계 확인용 `trex-web` `b72ccd7cd639447e71d7e785857d2c503e3a1841` +- **승인 경계:** 이 승인은 아래 `portfolio-hub` Discovery 범위를 승인한다. 구현 완료, `trex-web` 변경, main 반영, push, PR, 배포 또는 release 승인을 의미하지 않는다. + +### Problem + +포트폴리오 추가 화면에서 사용자가 `연결 데몬`을 선택해 생성해도 생성된 포트폴리오의 `managed_by_daemon_id`가 저장되지 않는다. 같은 포트폴리오를 편집하여 비-null 데몬을 다시 지정하면 반영된다. + +프런트엔드는 생성 요청에 선택값을 포함하지만, `portfolio-hub`의 `POST /v1/portfolios` 처리기가 해당 값을 저장소 생성 호출로 전달하지 않는다. 반면 `PATCH /v1/portfolios/{portfolio_id}`는 비-null `managed_by_daemon_id`를 update fields에 포함하므로 “생성에서는 누락되고 편집에서는 반영”되는 제보와 코드 경로가 일치한다. + +### Evidence + +- Live issue: https://github.com/fourblocks2020/portfolio-hub/issues/27 + - 원래 제보: “편집으로 다시 지정해야 반영 됨.” + - 최초 작성: `2026-07-21T14:42:15Z` +- `trex-web/features/portfolio/portfolio-create-form.tsx:83-91` + - 선택한 데몬을 `managed_by_daemon_id: selectedDaemonId || null`로 생성 mutation에 전달한다. +- `trex-web/lib/api/portfolio-hub.ts:87-95,251-256` + - 생성 요청 타입과 `POST /v1/portfolios` wrapper가 `managed_by_daemon_id`를 허용하고 payload를 그대로 전송한다. +- `portfolio-hub/app/schemas/v1.py:235-258` + - 생성·수정 schema 모두 `managed_by_daemon_id` 입력을 허용한다. +- `portfolio-hub/app/api/v1_portfolios.py:170-183` + - 생성 handler가 `repo.create_portfolio(...)`에 `managed_by_daemon_id`를 전달하지 않는다. +- `portfolio-hub/app/repositories/portfolio_repository.py:150-165` + - 현재 생성 port 시그니처에도 `managed_by_daemon_id`가 없다. +- `portfolio-hub/app/api/v1_portfolios.py:222-235` + - 편집 handler는 `exclude_none=True`로 얻은 비-null fields를 `repo.update_portfolio(...)`에 전달하므로 비-null 데몬 재지정은 저장된다. +- `portfolio-hub/app/api/v1_daemons.py:26-56` + - 일반 daemon 목록은 호출자 소유 범위이며 active와 inactive를 `is_active`로 구분해 반환한다. +- `portfolio-hub/docs/data-model.md:231-259` + - daemon ID는 server-generated UUID이고, daemon은 사용자 소유이며, service token 바인딩에는 존재·active·동일 소유자 규칙이 적용된다. 비활성화는 토큰을 revoke하지만 기존 portfolio reference 자동 제거는 규정하지 않는다. +- `portfolio-hub/docs/interface-contract.md:301-315` + - 포트폴리오 생성·수정 API가 `managed_by_daemon_id`를 입력으로 공개하며 portfolio 소유권과 오류 계약의 기존 경계를 정의한다. + +### 승인된 결정 + +1. 이 이슈는 `portfolio-hub`가 소유하는 repo-local 수정만 다룬다. `trex-web` 코드·테스트와 필수 `strategy_id` UI 적응은 이 이슈의 범위 밖이다. +2. 포트폴리오 생성·수정에서 비-null daemon을 지정하려면 지정 시점에 daemon이 존재하고 active이며 포트폴리오 소유자와 같은 사용자가 소유해야 한다. +3. admin이 다른 사용자의 포트폴리오를 생성·수정할 때도 daemon 소유자는 호출 admin이 아니라 대상 포트폴리오 소유자와 일치해야 한다. +4. 미존재 또는 배정 권한이 없는 foreign daemon은 존재를 숨겨 HTTP 404, 소유 범위 안의 inactive daemon은 HTTP 409, UUID 형식이 아닌 daemon ID는 HTTP 422로 반환한다. +5. `PATCH`의 명시적 `managed_by_daemon_id: null` 해제 결함은 별도 이슈로 추적하고 이번 범위에 포함하지 않는다. + +### Scope + +- `POST /v1/portfolios`가 요청의 비-null `managed_by_daemon_id`를 생성 persistence까지 전달한다. +- 생성과 PATCH의 비-null daemon 지정·재지정은 같은 validation 계약을 사용한다. +- 요청 daemon ID는 daemon의 server-generated UUID 형식이어야 한다. +- daemon은 portfolio persistence 시점에 존재하고 active여야 하며 `daemon.owner_user_id == portfolio.owner_user_id`여야 한다. +- admin의 `user_id` override 생성에서는 최종 결정된 target `owner_user_id`를 daemon 소유권 비교 기준으로 사용한다. +- 미존재·foreign daemon은 HTTP 404, inactive daemon은 HTTP 409, malformed daemon ID는 HTTP 422로 실패한다. +- validation 실패 시 portfolio 생성·수정과 관련 부수 상태가 부분 반영되지 않는다. +- daemon validation과 portfolio write는 동시 변경에 대해 하나의 원자적 판정 경계를 가져야 한다. assignment가 먼저 확정된 뒤 daemon이 비활성화되면 기존 deactivation 계약에 따라 service token은 revoke되고 portfolio reference는 보존된다. 비활성화가 먼저 확정되면 새 assignment는 HTTP 409로 실패한다. +- 데몬을 선택하지 않은 생성 요청은 `managed_by_daemon_id = null`인 unassigned portfolio를 생성하는 기존 동작을 유지한다. +- 성공한 생성 응답과 후속 portfolio 조회는 같은 `managed_by_daemon_id`를 반환한다. +- 기존 비-null PATCH 지정·재지정 경로에는 위 validation을 적용하되, 포트폴리오 소유권·disabled 보호·service/client 거부 등 기존 mutation 계약을 유지한다. +- `portfolio-hub` 단위/API 테스트와 disposable PostgreSQL 통합 테스트로 성공·미선택·malformed·미존재·foreign·inactive·admin target-owner·동시성·`POST → GET` persistence를 검증한다. + +### 승인된 수용 기준 후보 + +- [ ] 소유자의 active daemon을 선택해 포트폴리오를 생성하면 HTTP 201 응답의 `managed_by_daemon_id`가 선택한 ID와 같다. +- [ ] 같은 포트폴리오를 후속 조회해도 선택한 daemon ID가 보존된다. +- [ ] daemon 미선택 생성은 `managed_by_daemon_id = null`을 유지한다. +- [ ] UUID 형식이 아닌 daemon ID는 HTTP 422이며 portfolio가 생성·수정되지 않는다. +- [ ] 미존재 또는 portfolio 소유자와 다른 사용자의 daemon ID는 HTTP 404이며 portfolio가 생성·수정되지 않는다. +- [ ] portfolio 소유자 범위의 inactive daemon ID는 HTTP 409이며 portfolio가 생성·수정되지 않는다. +- [ ] admin target-user 생성·수정은 target portfolio owner 소유의 active daemon만 허용한다. +- [ ] 비-null PATCH 지정·재지정도 생성과 동일한 daemon validation과 오류 계약을 적용한다. +- [ ] assignment와 daemon 비활성화가 경합해도 inactive daemon에 새로운 assignment가 확정되는 중간 상태를 허용하지 않는다. +- [ ] assignment 확정 후 daemon 비활성화는 기존 portfolio reference를 자동 제거하지 않고 기존 token-revocation 계약을 유지한다. +- [ ] 실제 PostgreSQL persistence 경계에서 `POST → GET` 보존과 실패 시 무변경이 검증된다. +- [ ] 기존 portfolio 생성·수정의 인증, 소유권, strategy, asset, disabled-state 계약이 회귀하지 않는다. + +### Out of Scope + +- `trex-web` 코드, 타입, 화면 또는 테스트 변경 +- `trex-web`의 `/v1/strategies` 조회·선택과 필수 `strategy_id` 계약 적응 +- `PATCH /v1/portfolios/{id}`의 명시적 `managed_by_daemon_id: null` 해제 지원 +- 데몬 실행·중지·재시작 또는 배포 자동화 +- 데몬 credential 관리 정책 변경 +- 포트폴리오의 자동 데몬 배정 +- daemon 비활성화 시 기존 portfolio reference 자동 해제 또는 다른 daemon으로 자동 이전 +- 기존 데이터 전체에 대한 일괄 backfill +- 내부 `daemons` 또는 `portfolios` 식별자·테이블 구조 변경 +- 이 Discovery 승인만으로 구현, main 반영, push, PR, migration 실행, 배포 또는 release 수행 + +### Risks and Dependencies + +- `managed_by_daemon_id`는 DB-level FK가 없는 soft reference이므로 validation과 write를 분리하면 daemon 상태·소유권 변경과의 TOCTOU 위험이 생긴다. persistence transaction과 적절한 row-lock/동등한 직렬화 경계가 필요하다. +- daemon 소유자 재할당 기능이 있으므로 portfolio assignment와 동시 실행될 때도 최종 관계가 동일 소유자 규칙을 위반하지 않아야 한다. +- `trex-web`의 필수 `strategy_id` 계약 드리프트는 별도 범위다. 이번 backend 동작은 유효한 `strategy_id`를 포함한 직접 API 요청으로 독립 검증한다. +- 명시적 null 해제는 알려진 별도 결함으로 남으며 이번 승인 결과를 그 기능의 완료로 해석하지 않는다. + +## Approval State + +- **Status:** APPROVED +- **Approved by:** genichin +- **Approved at:** 2026-08-15T03:02:35Z +- **Approval basis:** OQ-001=A, OQ-002=A, OQ-003=A, OQ-004=A의 단일 선택과 `trex-web` 관련 수정·테스트를 Out of Scope로 두라는 요청자의 명시적 지시 +- **Unresolved Open Questions:** 없음 +- **Confirmation:** Discovery 검토·승인 완료 +- **Authority boundary:** 위 `portfolio-hub` Discovery 범위만 승인하며 구현 완료, lifecycle closure, main 반영, push, PR, 배포 또는 release를 의미하지 않는다. diff --git a/.proofline/lines/line-0004/line-0004.md b/.proofline/lines/line-0004/line-0004.md new file mode 100644 index 0000000..daa512d --- /dev/null +++ b/.proofline/lines/line-0004/line-0004.md @@ -0,0 +1,3 @@ +--- +id: "line-0004" +---