docs(proofline): record issue 23 specification

This commit is contained in:
2026-08-13 14:47:14 +09:00
parent 8dcdbdc9e0
commit 92df8e0c5a
11 changed files with 362 additions and 2 deletions
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0001"
status: active
---
# 포트폴리오 목록의 ledger 투자금 집계 필드
## Criterion
`GET /v1/portfolios`의 각 `PortfolioItem`은 기존 응답 필드를 유지하면서 `net_contributed_capital`과 `cumulative_profit_withdrawn`을 float 값으로 포함해야 한다. 전자는 순투자현금, 후자는 누적 수익 인출액을 뜻하며, 해당 portfolio에 유효한 manual ledger가 없거나 집계 결과가 없으면 두 값은 각각 `0.0`이어야 한다.
## Verification
- 성공 조건: ledger 집계가 있는 portfolio는 두 필드에 해당 집계값을 반환하고, ledger 집계가 없는 portfolio는 두 필드를 `0.0`으로 반환한다.
- 실패·경계 조건: 빈 목록은 `portfolios: []`, `total: 0`을 반환하며 신규 필드 추가 때문에 기존 응답 필드가 제거되거나 값의 의미가 바뀌지 않는다.
- 검사 방법: `tests/test_v1_portfolios_list.py`의 목록 집계값 결합·기본값·빈 목록·기존 필드 회귀 테스트와 OpenAPI schema의 `PortfolioItem`을 검사한다.
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0002"
status: active
---
# REVERSAL-aware manual ledger 산식과 목록·단건 일치
## Criterion
`net_contributed_capital`은 `REVERSAL`되지 않은 manual `INVEST` 합계에서 `REVERSAL`되지 않은 manual `WITHDRAW` 합계를 뺀 값이어야 하고, `cumulative_profit_withdrawn`은 `REVERSAL`되지 않은 manual `PROFIT` 합계여야 한다. `REVERSAL`이 참조한 `INVEST`, `WITHDRAW`, `PROFIT` 원거래는 모든 return aggregate에서 제외하고 `REVERSAL` 자체는 금액으로 합산하지 않으며, 목록과 단건 `returns/summary`는 같은 유효 원장 판정과 네 집계값 의미를 사용해야 한다.
## Verification
- 성공 조건: 미취소 `INVEST`, `WITHDRAW`, `PROFIT` 조합에서 `PROFIT`은 `WITHDRAW`와 분리되고 목록과 단건 summary의 `total_invested`, `total_withdrawn`, `net_contributed_capital`, `cumulative_profit_withdrawn`이 일치한다.
- 실패·경계 조건: `INVEST`, `WITHDRAW`, `PROFIT`을 각각 reversal하면 해당 원거래 효과가 제거되며 reversal row 자체가 합계에 더해지지 않는다.
- 검사 방법: `tests/test_portfolio_ledger_aggregates.py`의 shared batch semantics와 `tests/test_v1_portfolios_manual_ledger.py`의 profit 분리 및 세 action reversal 회귀를 실행한다.
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0003"
status: active
---
# 다중 포트폴리오 grouped aggregate와 N+1 방지
## Criterion
목록용 repository 계약은 반환 대상인 여러 portfolio ID의 유효 manual ledger 집계를 하나의 grouped aggregate query로 계산해 portfolio ID별 결과를 반환해야 하며, 목록 크기에 비례하는 portfolio별 ledger query를 수행해서는 안 된다. 입력 ID 목록이 비어 있으면 schema 확인이나 DB 연결·query 없이 빈 mapping을 반환해야 한다.
## Verification
- 성공 조건: 둘 이상의 portfolio ID를 한 번에 전달했을 때 단일 query가 실행되고 각 ID의 합계가 독립된 mapping으로 반환된다.
- 실패·경계 조건: 빈 ID 입력은 `{}`를 반환하고 DB connection 및 schema/query 호출이 발생하지 않는다.
- 검사 방법: `tests/test_portfolio_ledger_aggregates.py`에서 `GROUP BY ledger.portfolio_id`, portfolio ID 배열 parameter, 단일 `execute`, 빈 입력 no-call을 검사하고 `tests/test_v1_portfolios_list.py`에서 route가 summary batch를 한 번 요청하는지 확인한다.
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0004"
status: active
---
# 목록 가시성·필터 범위의 ledger 집계 격리
## Criterion
`GET /v1/portfolios`는 기존 client token 거부, service token daemon scope, session owner scope, admin `user_id` scope와 `include_disabled`, `market` 필터를 적용한 뒤 실제 반환할 portfolio ID만 ledger aggregate에 전달해야 한다. 반환 범위 밖 portfolio의 ledger를 집계 요청하거나 응답에 노출해서는 안 되며, 여러 portfolio의 금액은 ID 사이에서 섞이지 않아야 한다.
## Verification
- 성공 조건: owner/admin/service 및 `include_disabled`·`market` 조합마다 반환된 portfolio ID 목록과 aggregate 요청 ID 목록이 정확히 일치하고 각 항목은 자기 ID의 합계만 받는다.
- 실패·경계 조건: 권한 또는 필터로 제외된 portfolio ID는 aggregate 요청과 응답 모두에 나타나지 않고, client token 거부와 기존 scope 동작이 유지된다.
- 검사 방법: `tests/test_v1_portfolios_list.py`에서 fake repository의 `summary_requests` 기록과 기존 인증·필터 matrix를 함께 검사한다.
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0005"
status: active
---
# additive API·문서 호환성과 reversal 회귀 보존
## Criterion
ledger 투자금 집계 확장은 기존 `GET /v1/portfolios` 및 단건 returns summary 계약에 대한 additive 변경이어야 한다. OpenAPI와 repository-owned interface/runtime 문서는 `net_contributed_capital`을 순투자현금, `cumulative_profit_withdrawn`을 누적 수익 인출액으로 정의하고 `portfolio-hub`가 ledger 권위 값을 제공함을 명시해야 하며, 기존 이중 reversal 거부 동작을 유지해야 한다.
## Verification
- 성공 조건: 기존 목록 필드와 token/filter 동작이 보존되고 `docs/interface-contract.md`, `docs/runtime-flows.md`, OpenAPI schema가 두 필드의 산식·REVERSAL·권위 경계를 일관되게 설명한다.
- 실패·경계 조건: `PROFIT`을 `WITHDRAW` 또는 전체 실현손익으로 설명하지 않고, 동일 원거래를 두 번 reversal하려는 기존 요청은 계속 거부된다.
- 검사 방법: 목록·manual ledger 회귀 테스트와 문서·OpenAPI 검사를 실행하고 focused suite 및 전체 프로젝트 suite를 사용해 기존 동작 회귀 여부를 판정한다.