feat(dotnet): add domain parity backlog harness
CI Workflow Lint / validate-ci-workflow-lint (push) Failing after 14s
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 23s
Validators (Pushes and Pull Requests) / validate-core (push) Successful in 2m10s

This commit is contained in:
2026-07-13 00:36:41 +09:00
parent a45961928e
commit 3fbb5ea2bf
7 changed files with 194 additions and 0 deletions
+3
View File
@@ -190,6 +190,9 @@ jobs:
- name: Validate Dotnet CICD Chain Contract
run: python3 tools/validate_dotnet_cicd_chain_contract_v1.py
- name: Validate Dotnet Domain Parity Backlog
run: python3 tools/validate_dotnet_domain_parity_backlog_v1.py
- name: Build Calibration Priority Backlog
+2
View File
@@ -105,6 +105,7 @@
- `docs/WBS_10_DOTNET_NORMALIZATION_CONTRACT.yaml`: WBS-10 normalization/read model 계약.
- `docs/WBS_10_DOTNET_IDEMPOTENCY_CONTRACT.yaml`: WBS-10 idempotency/lock 계약.
- `docs/WBS_10_DOTNET_CICD_CHAIN_CONTRACT.yaml`: WBS-10 CI/CD 순차 게이트 계약.
- `docs/WBS_10_DOTNET_DOMAIN_PARITY_BACKLOG.yaml`: WBS-10 domain parity backlog contract.
- `tools/validate_dotnet_migration_roadmap_v1.py`: WBS-10 상세 로드맵 YAML validator.
- `tools/validate_dotnet_migration_execution_plan_v1.py`: WBS-10 실행 분해 계획 validator.
- `tools/validate_dotnet_parity_contract_v1.py`: WBS-10 parity 계약 validator.
@@ -113,6 +114,7 @@
- `tools/validate_dotnet_normalization_contract_v1.py`: WBS-10 normalization 계약 validator.
- `tools/validate_dotnet_idempotency_contract_v1.py`: WBS-10 idempotency 계약 validator.
- `tools/validate_dotnet_cicd_chain_contract_v1.py`: WBS-10 CI/CD chain 계약 validator.
- `tools/validate_dotnet_domain_parity_backlog_v1.py`: WBS-10 domain parity backlog validator.
- `Temp/snapshot_admin_approval_packet_v1.json`: snapshot admin approval packet export.
- `Temp/snapshot_admin_approval_packet_v1.md`: snapshot admin approval packet summary.
- `Temp/`: 실행 결과와 캐시. 라우팅 대상은 아니며 runtime consumer만 읽는다.
+1
View File
@@ -1474,6 +1474,7 @@ WBS-8.8 (KIS 리팩터) — 독립적 (원격 병행)
> normalization contract: [WBS_10_DOTNET_NORMALIZATION_CONTRACT.yaml](./WBS_10_DOTNET_NORMALIZATION_CONTRACT.yaml)
> idempotency contract: [WBS_10_DOTNET_IDEMPOTENCY_CONTRACT.yaml](./WBS_10_DOTNET_IDEMPOTENCY_CONTRACT.yaml)
> ci/cd chain contract: [WBS_10_DOTNET_CICD_CHAIN_CONTRACT.yaml](./WBS_10_DOTNET_CICD_CHAIN_CONTRACT.yaml)
> domain parity backlog: [WBS_10_DOTNET_DOMAIN_PARITY_BACKLOG.yaml](./WBS_10_DOTNET_DOMAIN_PARITY_BACKLOG.yaml)
> 현황 진단(2026-06-26): .NET 프로젝트는 Python 엔진(41 모듈, 14,500 LOC) 대비 5~10%(~1,400 LOC) 수준.
> Domain 계산기 6개·데이터 모델 8개·KIS/Naver/Yahoo 클라이언트·PostgreSQL 마이그레이션·Razor Pages 어드민 대시보드 기본 구현 완료.
@@ -0,0 +1,50 @@
formula_id: WBS_10_DOTNET_DOMAIN_PARITY_BACKLOG_V1
owner: QuantEngine
status: draft
goal: "핵심 계산기 parity 대상과 우선순위를 고정한다."
coverage_min: 1.0
tolerance_policy:
numeric_default: 0
text_default: exact
factor_calculator_numeric: 0.000001
exit_stop_price_numeric: 0.0001
parity_targets:
- target_id: formula_engine_timing
source: src/dotnet/QuantEngine.Core/Domain/FormulaEngine.cs
priority: 1
reason: "timing decision은 downstream routing의 선행 게이트"
- target_id: formula_engine_sell
source: src/dotnet/QuantEngine.Core/Domain/FormulaEngine.cs
priority: 2
reason: "sell ratio/action은 실행 표 생성의 핵심"
- target_id: formula_engine_final
source: src/dotnet/QuantEngine.Core/Domain/FormulaEngine.cs
priority: 3
reason: "final decision은 보고/배포의 최종 산출"
- target_id: exit_stop_price
source: src/dotnet/QuantEngine.Core/Domain/ExitDecisions.cs
priority: 4
reason: "손절가 절대값 일치가 필요"
- target_id: exit_stop_ladder
source: src/dotnet/QuantEngine.Core/Domain/ExitDecisions.cs
priority: 5
reason: "워터폴 선형 처리 검증"
- target_id: exit_heat_thresholds
source: src/dotnet/QuantEngine.Core/Domain/ExitDecisions.cs
priority: 6
reason: "동적 열감 임계값 일치"
- target_id: factor_calculator
source: src/dotnet/QuantEngine.Core/Domain/FactorCalculator.cs
priority: 7
reason: "정규화된 입력 순서에서 deterministic output 보장"
coverage_rule:
- "priority 1..7 모두 존재해야 한다"
- "tolerance_policy는 숫자/텍스트 기본값을 정의해야 한다"
- "새 parity target은 reference fixture와 함께만 추가한다"
notes:
- "parity는 기능 추가가 아니라 회귀 차단 장치다."
- "수치 재계산은 reference fixture 외에서 하지 않는다."
+16
View File
@@ -2409,6 +2409,22 @@ dag:
- Temp/wbs_10_dotnet_cicd_chain_contract_v1.json
strict: true
timeout_sec: 60
validate_dotnet_domain_parity_backlog:
artifact_policy: keep
cache_key: validate_dotnet_domain_parity_backlog_v1
command:
- python
- tools/validate_dotnet_domain_parity_backlog_v1.py
depends_on: []
id: validate_dotnet_domain_parity_backlog
inputs:
- tools/validate_dotnet_domain_parity_backlog_v1.py
- docs/WBS_10_DOTNET_DOMAIN_PARITY_BACKLOG.yaml
note: WBS-10 핵심 계산기 parity 대상과 우선순위를 검증한다.
outputs:
- Temp/wbs_10_dotnet_domain_parity_backlog_v1.json
strict: true
timeout_sec: 60
validate_specs:
artifact_policy: keep
cache_key: validate_specs_v1
@@ -0,0 +1,38 @@
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
def test_validate_dotnet_domain_parity_backlog_passes() -> None:
root = Path(__file__).resolve().parents[2]
proc = subprocess.run(
[sys.executable, str(root / "tools" / "validate_dotnet_domain_parity_backlog_v1.py")],
cwd=root,
capture_output=True,
text=True,
)
assert proc.returncode == 0, proc.stdout + proc.stderr
payload = json.loads(proc.stdout)
assert payload["gate"] == "PASS"
def test_validate_dotnet_domain_parity_backlog_reports_missing_target() -> None:
root = Path(__file__).resolve().parents[2]
temp = root / "Temp" / "test_wbs_10_domain_parity_backlog_bad.yaml"
temp.write_text(
"formula_id: WBS_10_DOTNET_DOMAIN_PARITY_BACKLOG_V1\ncoverage_min: 1.0\ntolerance_policy:\n numeric_default: 0\n text_default: exact\nparity_targets:\n - target_id: formula_engine_timing\n priority: 1\n - target_id: formula_engine_sell\n priority: 2\n",
encoding="utf-8",
)
proc = subprocess.run(
[sys.executable, str(root / "tools" / "validate_dotnet_domain_parity_backlog_v1.py"), "--backlog", str(temp)],
cwd=root,
capture_output=True,
text=True,
)
assert proc.returncode != 0
payload = json.loads(proc.stdout)
assert payload["gate"] == "FAIL"
assert "exit_stop_price missing" in payload["missing"]
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
import yaml
REQUIRED_TARGETS = {
"formula_engine_timing",
"formula_engine_sell",
"formula_engine_final",
"exit_stop_price",
"exit_stop_ladder",
"exit_heat_thresholds",
"factor_calculator",
}
def load_yaml(path: Path) -> dict[str, Any]:
if not path.exists():
raise FileNotFoundError(path)
return yaml.safe_load(path.read_text(encoding="utf-8"))
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Validate WBS-10 dotnet domain parity backlog")
parser.add_argument("--backlog", default="docs/WBS_10_DOTNET_DOMAIN_PARITY_BACKLOG.yaml")
args = parser.parse_args(argv)
backlog_path = Path(args.backlog).resolve()
payload: dict[str, Any] = {
"formula_id": "WBS_10_DOTNET_DOMAIN_PARITY_BACKLOG_V1",
"gate": "FAIL",
"missing": [],
"evidence": {"backlog": str(backlog_path)},
}
try:
data = load_yaml(backlog_path)
except FileNotFoundError:
payload["missing"].append("backlog missing")
print(json.dumps(payload, ensure_ascii=False, indent=2))
return 1
if data.get("formula_id") != "WBS_10_DOTNET_DOMAIN_PARITY_BACKLOG_V1":
payload["missing"].append("formula_id")
if data.get("coverage_min") != 1.0:
payload["missing"].append("coverage_min")
tolerance = data.get("tolerance_policy") or {}
if tolerance.get("numeric_default") != 0:
payload["missing"].append("tolerance_policy.numeric_default")
if tolerance.get("text_default") != "exact":
payload["missing"].append("tolerance_policy.text_default")
targets = data.get("parity_targets") or []
target_ids = {item.get("target_id", "") for item in targets}
missing_targets = REQUIRED_TARGETS - target_ids
for target in sorted(missing_targets):
payload["missing"].append(f"{target} missing")
priorities = [item.get("priority") for item in targets]
if priorities != sorted(priorities):
payload["missing"].append("priority order")
payload["gate"] = "PASS" if not payload["missing"] else "FAIL"
payload["message"] = (
"WBS-10 dotnet domain parity backlog validation passed."
if payload["gate"] == "PASS"
else "WBS-10 dotnet domain parity backlog validation failed."
)
out_path = backlog_path.parent.parent / "Temp" / "wbs_10_dotnet_domain_parity_backlog_v1.json"
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(payload, ensure_ascii=False, indent=2))
return 0 if payload["gate"] == "PASS" else 1
if __name__ == "__main__":
raise SystemExit(main())