docs: record ProofLine specification for issue 28

This commit is contained in:
2026-08-19 17:42:22 +09:00
parent 5eb8c04266
commit fe22be0b66
11 changed files with 554 additions and 21 deletions
+6 -5
View File
@@ -3,14 +3,15 @@ id: "ac-0001"
status: active
---
# 포트폴리오 목록의 ledger 투자금 집계 필드
# 포트폴리오 목록의 source-independent 투자금 집계 필드
## Criterion
`GET /v1/portfolios`의 각 `PortfolioItem`은 기존 응답 필드를 유지하면서 `net_contributed_capital`과 `cumulative_profit_withdrawn`을 float 값으로 포함해야 한다. 전자는 순투자현금, 후자는 누적 수익 인출액을 뜻하며, 해당 portfolio에 유효한 manual ledger가 없거나 집계 결과가 없으면 두 값은 각각 `0.0`이어야 한다.
`GET /v1/portfolios`의 각 `PortfolioItem`은 기존 응답 필드를 유지하면서 `net_contributed_capital`과 `cumulative_profit_withdrawn`을 float 값으로 포함해야 한다. 전자는 모든 source의 유효한 `INVEST` gross 합계에서 유효한 `WITHDRAW` 합계를 뺀 순투자원금이고, 후자는 원금과 분리한 모든 source의 유효한 `PROFIT` 합계다. 해당 portfolio에 유효한 대상 ledger event가 없으면 두 값은 각각 `0.0`이어야 한다.
## Verification
- 성공 조건: ledger 집계가 있는 portfolio는 두 필드에 해당 집계값을 반환하고, ledger 집계가 없는 portfolio는 두 필드를 `0.0`으로 반환한다.
- 실패·경계 조건: 빈 목록은 `portfolios: []`, `total: 0`을 반환하며 신규 필드 추가 때문에 기존 응답 필드가 제거되거나 값의 의미가 바뀌지 않는다.
- 검사 방법: `tests/test_v1_portfolios_list.py`의 목록 집계값 결합·기본값·빈 목록·기존 필드 회귀 테스트와 OpenAPI schema의 `PortfolioItem`을 검사한다.
- 성공 조건: broker-only와 manual/broker mixed-source portfolio가 source와 무관한 side별 합계로 두 필드를 반환하고, ledger 집계가 없는 portfolio는 두 필드를 `0.0`으로 반환한다.
- 실패·경계 조건: `PROFIT`이 원금에 포함되거나 `buffer_out` 등 다른 side가 `INVEST`로 재해석되거나, 기존 응답 필드·token/filter 동작이 변경되면 실패한다.
- 검사 방법: `tests/test_portfolio_ledger_aggregates.py`, `tests/test_v1_portfolios_list.py`, `tests/test_v1_portfolios_manual_ledger.py`와 disposable PostgreSQL integration을 실행한다.
- 현재 구현 평가: focused `39 passed`, actual PostgreSQL semantic integration `1 passed`, clean full regression `661 passed, 16 skipped`이며 독립 재QC가 PASS했다.
+6 -5
View File
@@ -3,14 +3,15 @@ id: "ac-0002"
status: active
---
# REVERSAL-aware manual ledger 산식과 목록·단건 일치
# Source-independent REVERSAL 산식과 목록·단건 일치
## 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`는 같은 유효 원장 판정과 네 집계값 의미를 사용해야 한다.
`total_invested`는 모든 source의 유효한 `INVEST` gross 합계, `total_withdrawn`은 유효한 `WITHDRAW` 합계, `net_contributed_capital`은 두 값의 차, `cumulative_profit_withdrawn`은 원금에서 제외한 유효한 `PROFIT` 합계여야 한다. 동일 portfolio에서 matching `REVERSAL.target_ledger_entry_id`로 참조된 original `INVEST`, `WITHDRAW`, `PROFIT`은 original과 reversal의 source와 무관하게 모든 return aggregate에서 제외하고 `REVERSAL` 자체는 금액으로 합산하지 않는다. 목록과 단건 `returns/summary`는 같은 repository aggregate와 네 집계값 의미를 사용해야 한다.
## 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 회귀를 실행한다.
- 성공 조건: broker-only와 mixed-source `INVEST`·`WITHDRAW`·`PROFIT` 조합에서 gross·withdrawn·net·profit 값이 승인 산식과 일치하고 목록과 단건 summary가 일치한다.
- 실패·경계 조건: manual reversal→broker original 또는 broker reversal→manual original에서 original 효과가 남거나, `PROFIT`이 원금에 섞이거나 reversal row 자체가 합산되면 실패한다.
- 검사 방법: SQL contract unit test와 실제 PostgreSQL 16 integration에서 production repository SQL, 양방향 cross-source reversal, `buffer_out` 제외를 검사한다.
- 현재 구현 평가: source predicate가 original과 reversal 양쪽에서 제거됐고 actual PostgreSQL semantic integration 및 독립 재QC가 PASS했다.
+6 -5
View File
@@ -3,14 +3,15 @@ id: "ac-0003"
status: active
---
# 다중 포트폴리오 grouped aggregate와 N+1 방지
# 다중 포트폴리오 source-independent grouped aggregate와 N+1 방지
## Criterion
목록용 repository 계약은 반환 대상인 여러 portfolio ID의 유효 manual ledger 집계를 하나의 grouped aggregate query로 계산해 portfolio ID별 결과를 반환해야 하며, 목록 크기에 비례하는 portfolio별 ledger query를 수행해서는 안 된다. 입력 ID 목록이 비어 있으면 schema 확인이나 DB 연결·query 없이 빈 mapping을 반환해야 한다.
목록용 repository 계약은 반환 대상인 여러 portfolio ID의 source-independent valid ledger aggregate를 하나의 grouped 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를 한 번 요청하는지 확인한다.
- 성공 조건: 둘 이상의 portfolio ID를 한 번에 전달했을 때 단일 query가 실행되고 broker/manual source가 섞여도 각 ID의 side별 합계가 독립된 mapping으로 반환된다.
- 실패·경계 조건: source predicate가 다시 도입되거나 portfolio별 N+1 query가 발생하거나, 빈 ID 입력에서 DB 작업이 발생하거나, portfolio ID 사이 금액이 섞이면 실패한다.
- 검사 방법: `tests/test_portfolio_ledger_aggregates.py`에서 `GROUP BY ledger.portfolio_id`, ID 배열 parameter, 단일 execute와 빈 입력 no-call을 검사하고 list route의 summary batch 요청을 검증한다.
- 현재 구현 평가: grouped query 구조와 empty-input fast path를 유지한 채 source predicate만 제거했으며 focused·full regression과 독립 재QC가 PASS했다.
+6 -5
View File
@@ -3,14 +3,15 @@ id: "ac-0005"
status: active
---
# additive API·문서 호환성과 reversal 회귀 보존
# API·문서 호환성과 query-only source-independent 교정
## Criterion
ledger 투자금 집계 확장은 기존 `GET /v1/portfolios` 및 단건 returns summary 계약에 대한 additive 변경이어야 한다. OpenAPI와 repository-owned interface/runtime 문서는 `net_contributed_capital`을 순투자현금, `cumulative_profit_withdrawn`을 누적 수익 인출액으로 정의하고 `portfolio-hub`가 ledger 권위 값을 제공함을 명시해야 하며, 기존 이중 reversal 거부 동작을 유지해야 한다.
Ledger 투자금 집계 교정은 기존 `GET /v1/portfolios` 및 단건 returns summary의 field 이름과 응답 구조를 유지해야 한다. OpenAPI schema와 repository-owned interface/data-model/runtime 문서는 source-independent valid `INVEST`·`WITHDRAW`·`PROFIT`, gross `total_invested`, `INVEST-WITHDRAW` net, 별도 `PROFIT`, source-independent matching reversal과 non-capital side 제외를 일관되게 설명해야 한다. 교정은 existing ledger row mutation/backfill 또는 `buffer_out` 재분류 없이 query semantic에 한정돼야 한다.
## Verification
- 성공 조건: 기존 목록 필드와 token/filter 동작이 보존되고 `docs/interface-contract.md`, `docs/runtime-flows.md`, OpenAPI schema가 두 필드의 산식·REVERSAL·권위 경계를 일관되게 설명한다.
- 실패·경계 조건: `PROFIT`을 `WITHDRAW` 또는 전체 실현손익으로 설명하지 않고, 동일 원거래를 두 번 reversal하려는 기존 요청은 계속 거부된다.
- 검사 방법: 목록·manual ledger 회귀 테스트와 문서·OpenAPI 검사를 실행하고 focused suite 및 전체 프로젝트 suite를 사용해 기존 동작 회귀 여부를 판정한다.
- 성공 조건: `app/schemas/v1.py`, `docs/interface-contract.md`, `docs/data-model.md`, `docs/runtime-flows.md`가 같은 산식·reversal·권위 경계를 설명하고 기존 field·auth·filter 계약을 보존한다.
- 실패·경계 조건: stale manual-only 문구가 남거나 `PROFIT`을 원금/전체 실현손익으로 설명하거나, API rename, ledger mutation/backfill, `buffer_out=INVEST` 재분류 또는 권한 우회가 발생하면 실패한다.
- 검사 방법: docs contract regression, API/list/manual ledger focused suite, 변경 diff와 migration/ledger write 부재를 검사한다.
- 현재 구현 평가: 최초 독립 QC가 발견한 stale interface contract를 보완하고 manual-only 문구 부재 회귀 테스트를 추가했으며 재QC는 PASS(Blocker 0 / Major 0 / Minor 0)했다.