From c2cd6437297c96d163c54b79fbe22ffcf25dc40c Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Mon, 13 Jul 2026 01:04:13 +0900 Subject: [PATCH] feat(dotnet): add domain parity artifact gate --- .gitea/workflows/ci.yml | 3 + AGENTS.md | 1 + docs/ROADMAP_WBS.md | 1 + spec/41_release_dag.yaml | 16 ++++++ ...lidate_dotnet_domain_parity_artifact_v1.py | 39 +++++++++++++ ...lidate_dotnet_domain_parity_artifact_v1.py | 57 +++++++++++++++++++ 6 files changed, 117 insertions(+) create mode 100644 tests/unit/test_validate_dotnet_domain_parity_artifact_v1.py create mode 100644 tools/validate_dotnet_domain_parity_artifact_v1.py diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 955fade5..1b49b645 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -196,6 +196,9 @@ jobs: - name: Validate Dotnet Read Model Contract run: python3 tools/validate_dotnet_read_model_contract_v1.py + - name: Validate Dotnet Domain Parity Artifact + run: python3 tools/validate_dotnet_domain_parity_artifact_v1.py + - name: Build Calibration Priority Backlog diff --git a/AGENTS.md b/AGENTS.md index 927ec4b8..3a3ade6e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,6 +116,7 @@ - `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. +- `tools/validate_dotnet_domain_parity_artifact_v1.py`: WBS-10 domain parity artifact validator. - `tools/validate_dotnet_read_model_contract_v1.py`: WBS-10 read model 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. diff --git a/docs/ROADMAP_WBS.md b/docs/ROADMAP_WBS.md index ea119639..4b556d65 100644 --- a/docs/ROADMAP_WBS.md +++ b/docs/ROADMAP_WBS.md @@ -1476,6 +1476,7 @@ WBS-8.8 (KIS 리팩터) — 독립적 (원격 병행) > 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) > read model contract: [WBS_10_DOTNET_READ_MODEL_CONTRACT.yaml](./WBS_10_DOTNET_READ_MODEL_CONTRACT.yaml) +> domain parity artifact validator: `tools/validate_dotnet_domain_parity_artifact_v1.py` > 현황 진단(2026-06-26): .NET 프로젝트는 Python 엔진(41 모듈, 14,500 LOC) 대비 5~10%(~1,400 LOC) 수준. > Domain 계산기 6개·데이터 모델 8개·KIS/Naver/Yahoo 클라이언트·PostgreSQL 마이그레이션·Razor Pages 어드민 대시보드 기본 구현 완료. diff --git a/spec/41_release_dag.yaml b/spec/41_release_dag.yaml index f568be27..70766c71 100644 --- a/spec/41_release_dag.yaml +++ b/spec/41_release_dag.yaml @@ -2441,6 +2441,22 @@ dag: - Temp/wbs_10_dotnet_read_model_contract_v1.json strict: true timeout_sec: 60 + validate_dotnet_domain_parity_artifact: + artifact_policy: keep + cache_key: validate_dotnet_domain_parity_artifact_v1 + command: + - python + - tools/validate_dotnet_domain_parity_artifact_v1.py + depends_on: [] + id: validate_dotnet_domain_parity_artifact + inputs: + - tools/validate_dotnet_domain_parity_artifact_v1.py + - Temp/dotnet_domain_parity_v1.json + note: WBS-10 C# parity fixture artifact의 PASS/total/passed 상태를 검증한다. + outputs: + - Temp/wbs_10_dotnet_domain_parity_artifact_v1.json + strict: true + timeout_sec: 60 validate_specs: artifact_policy: keep cache_key: validate_specs_v1 diff --git a/tests/unit/test_validate_dotnet_domain_parity_artifact_v1.py b/tests/unit/test_validate_dotnet_domain_parity_artifact_v1.py new file mode 100644 index 00000000..4c4614d5 --- /dev/null +++ b/tests/unit/test_validate_dotnet_domain_parity_artifact_v1.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +def test_validate_dotnet_domain_parity_artifact_passes() -> None: + root = Path(__file__).resolve().parents[2] + temp = root / "Temp" / "dotnet_domain_parity_v1.json" + temp.write_text('{"gate":"PASS","total":44,"passed":44}', encoding="utf-8") + + proc = subprocess.run( + [sys.executable, str(root / "tools" / "validate_dotnet_domain_parity_artifact_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_artifact_reports_bad_total() -> None: + root = Path(__file__).resolve().parents[2] + temp = root / "Temp" / "test_dotnet_domain_parity_bad.json" + temp.write_text('{"gate":"PASS","total":39,"passed":39}', encoding="utf-8") + + proc = subprocess.run( + [sys.executable, str(root / "tools" / "validate_dotnet_domain_parity_artifact_v1.py"), "--artifact", str(temp)], + cwd=root, + capture_output=True, + text=True, + ) + assert proc.returncode != 0 + payload = json.loads(proc.stdout) + assert payload["gate"] == "FAIL" + assert "total" in payload["missing"] diff --git a/tools/validate_dotnet_domain_parity_artifact_v1.py b/tools/validate_dotnet_domain_parity_artifact_v1.py new file mode 100644 index 00000000..52fcc3e2 --- /dev/null +++ b/tools/validate_dotnet_domain_parity_artifact_v1.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + + +def load_json(path: Path) -> dict[str, Any]: + if not path.exists(): + raise FileNotFoundError(path) + return json.loads(path.read_text(encoding="utf-8")) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Validate WBS-10 dotnet domain parity artifact") + parser.add_argument("--artifact", default="Temp/dotnet_domain_parity_v1.json") + args = parser.parse_args(argv) + + artifact_path = Path(args.artifact).resolve() + payload: dict[str, Any] = { + "formula_id": "WBS_10_DOTNET_DOMAIN_PARITY_ARTIFACT_V1", + "gate": "FAIL", + "missing": [], + "evidence": {"artifact": str(artifact_path)}, + } + + try: + data = load_json(artifact_path) + except FileNotFoundError: + payload["missing"].append("artifact missing") + print(json.dumps(payload, ensure_ascii=False, indent=2)) + return 1 + + if data.get("gate") != "PASS": + payload["missing"].append("gate") + if int(data.get("total", 0)) < 40: + payload["missing"].append("total") + if data.get("passed") != data.get("total"): + payload["missing"].append("passed") + + payload["gate"] = "PASS" if not payload["missing"] else "FAIL" + payload["message"] = ( + "WBS-10 dotnet domain parity artifact validation passed." + if payload["gate"] == "PASS" + else "WBS-10 dotnet domain parity artifact validation failed." + ) + + out_path = artifact_path.parent / "wbs_10_dotnet_domain_parity_artifact_v1.json" + 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())