Merge pull request 'process: enforce issue-status consistency for completion marks (#390)' (#391) from feature/issue-390-validate-completion-consistency into feature/v3-session-policy-stream
All checks were successful
Gitea CI / test (push) Successful in 32s

Reviewed-on: #391
This commit was merged in pull request #391.
This commit is contained in:
2026-03-02 10:38:21 +09:00
4 changed files with 88 additions and 9 deletions

View File

@@ -1,6 +1,6 @@
<!-- <!--
Doc-ID: DOC-REQ-001 Doc-ID: DOC-REQ-001
Version: 1.0.11 Version: 1.0.12
Status: active Status: active
Owner: strategy Owner: strategy
Updated: 2026-03-02 Updated: 2026-03-02
@@ -38,3 +38,7 @@ Updated: 2026-03-02
- `REQ-OPS-002`: 문서의 수치 정책은 원장에서만 변경한다. - `REQ-OPS-002`: 문서의 수치 정책은 원장에서만 변경한다.
- `REQ-OPS-003`: 구현 태스크는 반드시 테스트 태스크를 동반한다. - `REQ-OPS-003`: 구현 태스크는 반드시 테스트 태스크를 동반한다.
- `REQ-OPS-004`: 원본 계획 문서(`v2`, `v3`)는 `docs/ouroboros/source/` 경로를 단일 기준으로 사용한다. - `REQ-OPS-004`: 원본 계획 문서(`v2`, `v3`)는 `docs/ouroboros/source/` 경로를 단일 기준으로 사용한다.
## 변경 이력
- 2026-03-02: `v1.0.12` 문서 검증 게이트 강화(#390) 반영에 따라 정책 문서 동기화 체크를 수행했다. (`REQ-OPS-002`)

View File

@@ -9,7 +9,7 @@ Updated: 2026-03-02
# v2/v3 구현 감사 및 수익률 분석 보고서 # v2/v3 구현 감사 및 수익률 분석 보고서
작성일: 2026-02-28 작성일: 2026-02-28
최종 업데이트: 2026-03-02 (#369 model 보조 트리거 사양 정합화 반영) 최종 업데이트: 2026-03-02 (#377 kill-switch refresh 재시도 정책 반영)
대상 기간: 2026-02-25 ~ 2026-02-28 (실거래) 대상 기간: 2026-02-25 ~ 2026-02-28 (실거래)
분석 브랜치: `feature/v3-session-policy-stream` 분석 브랜치: `feature/v3-session-policy-stream`
@@ -45,7 +45,7 @@ Updated: 2026-03-02
| REQ-V3-001 | 모든 신호/주문/로그에 session_id 포함 | ⚠️ 부분 | 큐 intent에 `session_id` 누락 (`#375`) | | REQ-V3-001 | 모든 신호/주문/로그에 session_id 포함 | ⚠️ 부분 | 큐 intent에 `session_id` 누락 (`#375`) |
| REQ-V3-002 | 세션 전환 훅 + 리스크 파라미터 재로딩 | ✅ 완료 | 세션 경계 E2E 회귀(override 적용/해제 + 재로딩 실패 폴백) 보강 (`#376`) | | REQ-V3-002 | 세션 전환 훅 + 리스크 파라미터 재로딩 | ✅ 완료 | 세션 경계 E2E 회귀(override 적용/해제 + 재로딩 실패 폴백) 보강 (`#376`) |
| REQ-V3-003 | 블랙아웃 윈도우 정책 | ✅ 완료 | `src/core/blackout_manager.py` | | REQ-V3-003 | 블랙아웃 윈도우 정책 | ✅ 완료 | `src/core/blackout_manager.py` |
| REQ-V3-004 | 블랙아웃 큐 + 복구 시 재검증 | ⚠️ 부분 | 큐 포화 oldest-drop 정책으로 정합화 (`#371`), 재검증 강화는 `#328` 추적 | | REQ-V3-004 | 블랙아웃 큐 + 복구 시 재검증 | ✅ 완료 | DB 기록(`#324`), 재검증 강화(`#328`), 큐 포화 oldest-drop(`#371`) 반영 |
| REQ-V3-005 | 저유동 세션 시장가 금지 | ✅ 완료 | `src/core/order_policy.py` | | REQ-V3-005 | 저유동 세션 시장가 금지 | ✅ 완료 | `src/core/order_policy.py` |
| REQ-V3-006 | 보수적 백테스트 체결 (불리 방향) | ✅ 완료 | `src/analysis/backtest_execution_model.py` | | REQ-V3-006 | 보수적 백테스트 체결 (불리 방향) | ✅ 완료 | `src/analysis/backtest_execution_model.py` |
| REQ-V3-007 | FX 손익 분리 (전략 PnL vs 환율 PnL) | ⚠️ 부분 | 런타임 분리 계산/전달 적용 (`#370`), buy-side `fx_rate` 미관측 시 `fx_pnl=0` fallback | | REQ-V3-007 | FX 손익 분리 (전략 PnL vs 환율 PnL) | ⚠️ 부분 | 런타임 분리 계산/전달 적용 (`#370`), buy-side `fx_rate` 미관측 시 `fx_pnl=0` fallback |
@@ -89,13 +89,13 @@ Updated: 2026-03-02
- **해소**: 세션 경계 E2E 회귀 테스트를 추가해 override 적용/해제, 재로딩 실패 시 폴백 유지를 검증함 (`#376`) - **해소**: 세션 경계 E2E 회귀 테스트를 추가해 override 적용/해제, 재로딩 실패 시 폴백 유지를 검증함 (`#376`)
- **요구사항**: REQ-V3-002 - **요구사항**: REQ-V3-002
### GAP-4: 블랙아웃 복구 DB 기록 + 재검증 → ⚠️ 부분 해소 (#324, #328, #371) ### GAP-4: 블랙아웃 복구 DB 기록 + 재검증 → 해소 (#324, #328, #371)
- **위치**: `src/core/blackout_manager.py`, `src/main.py` - **위치**: `src/core/blackout_manager.py`, `src/main.py`
- **현 상태**: - **현 상태**:
- #324 추적 범위(DB 기록)는 구현 경로가 존재 - #324: 복구 주문 DB 기록 구현 및 테스트 반영
- #328 범위(가격/세션 재검증 강화)는 추적 이슈 오픈 상태 - #328: 가격/세션 재검증 강화 구현 및 머지 완료
- #371: 큐 포화 정책을 oldest-drop으로 명시/구현해 최신 intent 유실 경로 제거 - #371: 큐 포화 정책을 oldest-drop으로 명시/구현해 최신 intent 유실 경로 제거
- **요구사항**: REQ-V3-004 - **요구사항**: REQ-V3-004
### GAP-5: 시간장벽이 봉 개수 고정 → ✅ 해소 (#329) ### GAP-5: 시간장벽이 봉 개수 고정 → ✅ 해소 (#329)
@@ -328,7 +328,7 @@ Updated: 2026-03-02
| 블랙아웃 복구 주문 `log_trade()` 추가 (GAP-4) | #324 | ✅ 머지 | | 블랙아웃 복구 주문 `log_trade()` 추가 (GAP-4) | #324 | ✅ 머지 |
| 세션 전환 리스크 파라미터 동적 재로딩 (GAP-3) | #327 | ✅ 머지 | | 세션 전환 리스크 파라미터 동적 재로딩 (GAP-3) | #327 | ✅ 머지 |
| session_id 거래/의사결정 로그 명시 전달 (GAP-1, GAP-2) | #326 | ✅ 머지 | | session_id 거래/의사결정 로그 명시 전달 (GAP-1, GAP-2) | #326 | ✅ 머지 |
| 블랙아웃 복구 가격/세션 재검증 강화 (GAP-4 잔여) | #328 | ✅ 머지 | | 블랙아웃 복구 가격/세션 재검증 강화 (GAP-4) | #328 | ✅ 머지 |
**잔여 개선 항목:** **잔여 개선 항목:**
@@ -337,7 +337,6 @@ Updated: 2026-03-02
| P1 | US 시장 ATR 공급 경로 완성 (ROOT-5 잔여) | 중간 | | P1 | US 시장 ATR 공급 경로 완성 (ROOT-5 잔여) | 중간 |
| P1 | FX PnL 운영 활성화 (REQ-V3-007) | 낮음 | | P1 | FX PnL 운영 활성화 (REQ-V3-007) | 낮음 |
| P2 | pred_down_prob ML 모델 대체 (ROOT-5 잔여) | 높음 | | P2 | pred_down_prob ML 모델 대체 (ROOT-5 잔여) | 높음 |
| P2 | 세션 경계 E2E 통합 테스트 보강 (GAP-3 잔여) | 낮음 |
### 5.3 권장 실행 순서 ### 5.3 권장 실행 순서

View File

@@ -33,6 +33,9 @@ ALLOWED_PLAN_TARGETS = {
"2": (DOC_DIR / "source" / "ouroboros_plan_v2.txt").resolve(), "2": (DOC_DIR / "source" / "ouroboros_plan_v2.txt").resolve(),
"3": (DOC_DIR / "source" / "ouroboros_plan_v3.txt").resolve(), "3": (DOC_DIR / "source" / "ouroboros_plan_v3.txt").resolve(),
} }
ISSUE_REF_PATTERN = re.compile(r"#(?P<issue>\d+)")
ISSUE_DONE_PATTERN = re.compile(r"(?:✅|머지|해소|완료)")
ISSUE_PENDING_PATTERN = re.compile(r"(?:잔여|오픈 상태|추적 이슈)")
def iter_docs() -> list[Path]: def iter_docs() -> list[Path]:
@@ -119,6 +122,38 @@ def collect_req_traceability(
req_to_test.setdefault(req_id, set()).add(item_id) req_to_test.setdefault(req_id, set()).add(item_id)
def validate_issue_status_consistency(path: Path, text: str, errors: list[str]) -> None:
issue_done_lines: dict[str, list[int]] = {}
issue_pending_lines: dict[str, list[int]] = {}
for line_no, raw_line in enumerate(text.splitlines(), start=1):
line = raw_line.strip()
if not line:
continue
issue_ids = [m.group("issue") for m in ISSUE_REF_PATTERN.finditer(line)]
if not issue_ids:
continue
is_pending = bool(ISSUE_PENDING_PATTERN.search(line))
is_done = bool(ISSUE_DONE_PATTERN.search(line)) and not is_pending
if not is_pending and not is_done:
continue
for issue_id in issue_ids:
if is_done:
issue_done_lines.setdefault(issue_id, []).append(line_no)
if is_pending:
issue_pending_lines.setdefault(issue_id, []).append(line_no)
conflicted_issues = sorted(set(issue_done_lines) & set(issue_pending_lines))
for issue_id in conflicted_issues:
errors.append(
f"{path}: conflicting status for issue #{issue_id} "
f"(done at lines {issue_done_lines[issue_id]}, "
f"pending at lines {issue_pending_lines[issue_id]})"
)
def main() -> int: def main() -> int:
if not DOC_DIR.exists(): if not DOC_DIR.exists():
print(f"ERROR: missing directory {DOC_DIR}") print(f"ERROR: missing directory {DOC_DIR}")
@@ -140,6 +175,8 @@ def main() -> int:
text = path.read_text(encoding="utf-8") text = path.read_text(encoding="utf-8")
validate_metadata(path, text, errors, doc_ids) validate_metadata(path, text, errors, doc_ids)
validate_links(path, text, errors) validate_links(path, text, errors)
if path.name == "80_implementation_audit.md":
validate_issue_status_consistency(path, text, errors)
collect_ids(path, text, defs, refs) collect_ids(path, text, defs, refs)
collect_req_traceability(text, req_to_task, req_to_test) collect_req_traceability(text, req_to_task, req_to_test)

View File

@@ -79,3 +79,42 @@ def test_validate_links_avoids_duplicate_error_for_invalid_plan_link(tmp_path) -
assert len(errors) == 1 assert len(errors) == 1
assert "invalid plan link path" in errors[0] assert "invalid plan link path" in errors[0]
def test_validate_issue_status_consistency_reports_conflicts() -> None:
module = _load_module()
errors: list[str] = []
path = Path("docs/ouroboros/80_implementation_audit.md").resolve()
text = "\n".join(
[
"| REQ-V3-004 | 상태 | 부분 | `#328` 잔여 |",
"| 항목 | 상태 | ✅ 완료 | `#328` 머지 |",
]
)
module.validate_issue_status_consistency(path, text, errors)
assert len(errors) == 1
assert "conflicting status for issue #328" in errors[0]
def test_validate_issue_status_consistency_allows_done_only() -> None:
module = _load_module()
errors: list[str] = []
path = Path("docs/ouroboros/80_implementation_audit.md").resolve()
text = "| 항목 | 상태 | ✅ 완료 | `#371` 머지 |"
module.validate_issue_status_consistency(path, text, errors)
assert errors == []
def test_validate_issue_status_consistency_allows_pending_only() -> None:
module = _load_module()
errors: list[str] = []
path = Path("docs/ouroboros/80_implementation_audit.md").resolve()
text = "| 항목 | 상태 | 부분 | `#390` 추적 이슈 |"
module.validate_issue_status_consistency(path, text, errors)
assert errors == []