docs(proofline): record line 0004 QC remediation

This commit is contained in:
2026-08-15 13:21:04 +09:00
parent e90519ad39
commit 2abd67da97
3 changed files with 339 additions and 3 deletions
@@ -0,0 +1,226 @@
# line-0004 구현 에이전트 수정 재요청
- **대상 이슈:** `fourblocks2020/portfolio-hub#27`
- **대상 Line:** `line-0004`
- **작업 worktree:** `/home/genichin/workspace/trex-lead/repos/portfolio-hub/.worktrees/line-0004`
- **작업 브랜치:** `line/line-0004-implementation`
- **수정 기준 커밋:** `e2102b6f2b855b7679ab67818202ef706d9848fb`
- **범위 권위:** `.proofline/lines/line-0004/dcy-0004.md`
- **1차 QC 결과:** `.proofline/lines/line-0004/evidence/first-qc-review.md`
- **요청 상태:** QC 반려 수정 요청
## 1. 작업 원칙
기존 구현의 의미와 승인된 Discovery 범위를 변경하지 말고 아래 blocker 및 관련 회귀 검증만 수정한다. 새로운 제품 결정, 다른 서비스 계약 영향, 승인 범위와의 충돌이 발견되면 구현을 중단하고 팀장에게 반환한다.
`trex-web`은 명시적인 Out of Scope다. 해당 저장소의 코드·타입·화면·테스트를 변경하거나 실행하지 않는다.
## 2. 필수 수정 — 참조 중 daemon owner transfer 차단
현재 daemon이 portfolio에 배정된 뒤 `update_daemon_owner()`로 daemon 소유자를 변경하면 다음 불변식이 깨진다.
```text
daemon.owner_user_id == portfolio.owner_user_id
```
`update_daemon_owner()`에서 해당 daemon을 참조하는 portfolio가 하나라도 있으면 owner transfer가 원자적으로 실패하도록 수정한다.
필수 결과:
- 참조 중 daemon owner transfer: `409 Conflict`
- daemon owner: 변경되지 않음
- portfolio `managed_by_daemon_id`: 변경되지 않음
- 기존 broker credentials: 삭제되지 않음
- transaction 일부만 반영되는 상태: 금지
다음 자동 동작은 승인 범위 밖이므로 구현하지 않는다.
- portfolio assignment 자동 해제
- portfolio를 새 daemon owner에게 자동 이전
- 다른 daemon으로 자동 재배정
- 기존 데이터 backfill
## 3. 필수 수정 — assignment와 owner transfer 직렬화
portfolio assignment와 daemon owner transfer가 동일 daemon을 대상으로 동시에 실행될 때 일관된 row-lock 순서와 transaction 경계를 사용한다.
허용되는 결과:
1. owner transfer가 먼저 commit되면 이전 owner portfolio의 assignment는 foreign daemon으로 판단되어 실패한다.
2. assignment가 먼저 commit되면 owner transfer는 참조 충돌로 실패한다.
금지되는 최종 상태:
```text
portfolio.managed_by_daemon_id = daemon.id
portfolio.owner_user_id != daemon.owner_user_id
```
동시 실행이 timeout 또는 deadlock으로 끝나지 않도록 lock order를 검토한다.
## 4. 도메인 및 API 오류 계약
참조 중 owner transfer 충돌을 daemon 미존재 오류와 구분한다.
권장 구현 경계:
- 전용 domain error 추가
- repository `update_daemon_owner()`에서 전용 error 발생
- `PATCH /v1/admin/daemons/{daemon_id}`에서 HTTP 409로 매핑
- 기존 daemon 미존재 404 유지
- 기존 admin 인증·권한 계약 유지
- 미참조 owner transfer의 기존 credential invalidation 정책 유지
## 5. 필수 테스트
### 5.1 단위/API
다음을 추가하거나 보강한다.
- [ ] 미참조 daemon owner transfer 성공
- [ ] 참조 중 daemon owner transfer `409`
- [ ] `409` 후 daemon owner 무변경
- [ ] `409` 후 portfolio assignment 유지
- [ ] `409` 후 broker credentials 유지
- [ ] daemon 미존재 `404` 회귀 없음
- [ ] PATCH missing daemon `404` 및 portfolio 무변경
- [ ] admin PATCH에서 호출자 대신 target portfolio owner 기준 검증
- [ ] deactivation 후 기존 assignment reference 보존
- [ ] deactivation 시 기존 service token revoke 유지
### 5.2 실제 PostgreSQL
migration head가 적용된 disposable PostgreSQL에서 검증한다.
#### 순차 owner transfer
1. owner A daemon 생성
2. owner A portfolio에 daemon 배정
3. daemon을 owner B로 이전 시도
4. owner transfer 실패
5. 동일 소유자 불변식 유지
6. owner·assignment·credential 무변경 확인
#### assignment/owner-transfer race
1. 별도 connection/thread에서 assignment와 owner transfer 동시 실행
2. 두 실행 순서를 모두 유도하거나 반복 검증
3. 허용된 결과만 발생하는지 확인
4. 최종 DB 동일 소유자 불변식 확인
5. thread timeout/deadlock 없음 확인
#### 미참조 owner transfer
1. portfolio가 참조하지 않는 daemon owner transfer 성공
2. 기존 broker credential invalidation 정책 유지
## 6. PostgreSQL 실행 전제
완전히 빈 DB에는 먼저 migration head를 적용한다.
```bash
export TIMESCALEDB_HOST=127.0.0.1
export TIMESCALEDB_PORT=5432
export TIMESCALEDB_NAME=<disposable-db>
export TIMESCALEDB_USER=portfolio_hub_user
export TIMESCALEDB_PASSWORD=<local-test-password>
export TIMESCALEDB_SSLMODE=prefer
export PFHUB_DB_ENABLED=true
.venv/bin/pfhub migrate-db
PORTFOLIOHUB_RUN_POSTGRES_INTEGRATION=1 \
.venv/bin/python -m pytest -q \
tests/test_v1_portfolios_daemon_postgres_integration.py
```
실행 후 disposable DB를 삭제한다. 기존 공유·운영 DB를 오염시키지 않는다.
## 7. 문서 및 evidence
실제 동작과 일치하도록 필요한 범위에서 갱신한다.
- `docs/interface-contract.md`
- `docs/data-model.md`
- daemon admin owner-transfer runtime flow 문서
- `.proofline/lines/line-0004/evidence/implementation-progress.md`
문서에 다음을 명시한다.
- 참조 중 daemon owner transfer는 409
- owner transfer와 assignment의 원자적 직렬화
- 자동 assignment 해제·portfolio owner 변경 없음
- 충돌 시 owner·assignment·credential partial mutation 없음
`implementation-progress.md`의 “미커밋” 표기를 실제 상태로 수정하고 다음 실행 근거를 기록한다.
- QC 반려 blocker와 해결 방식
- 수정 파일 목록
- 수정 커밋
- 집중 테스트 결과
- 전체 회귀 결과
- 실제 PostgreSQL 순차·경합 검증 결과
- Ruff, compileall, diff check, ProofLine validate 결과
- push·PR·main 반영·배포·release 미수행 경계
QC 재검증 전에는 implementation complete 또는 lifecycle 완료 문서를 새로 만들지 않는다.
## 8. 최소 재검증
```bash
.venv/bin/python -m pytest -q \
tests/test_v1_portfolios_daemon_assignment.py \
tests/test_v1_portfolios_crud.py \
tests/test_bat022_req053_admin_daemon_crud.py
```
```bash
.venv/bin/python -m pytest -q
```
```bash
PORTFOLIOHUB_RUN_POSTGRES_INTEGRATION=1 \
.venv/bin/python -m pytest -q \
tests/test_v1_portfolios_daemon_postgres_integration.py
```
```bash
ruff check \
app/api/v1_portfolios.py \
app/api/v1_admin_daemons.py \
app/domain/errors.py \
app/repositories/portfolio_repository.py \
app/repositories/postgres.py \
tests/test_v1_portfolios_daemon_assignment.py \
tests/test_v1_portfolios_daemon_postgres_integration.py \
tests/test_bat022_req053_admin_daemon_crud.py
```
```bash
.venv/bin/python -m compileall -q app tests
git diff --check
proofline validate
```
## 9. 금지 사항
- `trex-web` 변경·테스트 금지
- approved Discovery 범위 변경 금지
- 명시적 `managed_by_daemon_id: null` 해제 구현 금지
- 자동 daemon 해제·이전·재배정·backfill 금지
- main 작업 금지
- push·PR·배포·release 금지
## 10. 반환 조건
수정·테스트·로컬 커밋 후 다음을 팀장에게 반환한다.
1. 새 커밋 SHA
2. 변경 파일 목록
3. blocker 해결 방식과 transaction/lock order
4. 테스트별 실제 pass/fail/skip 수
5. 실제 PostgreSQL 순차·경합 결과
6. 남은 비차단 위험
7. worktree clean 여부
수정 완료는 QC 재검증 입력일 뿐 QC PASS나 lifecycle 완료를 의미하지 않는다.