diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index afb099d3..dd964cf5 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -12,7 +12,7 @@ concurrency: cancel-in-progress: true env: - DOTNET_VERSION: '10.0.x' + DOTNET_VERSION: '9.0.x' jobs: # ======================================================================== @@ -74,7 +74,21 @@ jobs: echo "Applying $f" psql -U quantengine_ci -d quantenginedb -v ON_ERROR_STOP=1 -f "$f" done - echo "✓ Database migrations applied" + + # Verify migrations: check kis_*_audit tables exist + AUDIT_COUNT=$(psql -U quantengine_ci -d quantenginedb -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='quantengine' AND table_name LIKE 'kis_%_audit'") + if [ "$AUDIT_COUNT" -lt 3 ]; then + echo "ERROR: Expected 3 audit tables, found $AUDIT_COUNT" + exit 1 + fi + + # Verify triggers exist + TRIGGER_COUNT=$(psql -U quantengine_ci -d quantenginedb -t -c "SELECT COUNT(*) FROM information_schema.triggers WHERE trigger_schema='quantengine' AND trigger_name LIKE '%_audit_trigger'") + if [ "$TRIGGER_COUNT" -lt 3 ]; then + echo "WARNING: Expected 3 audit triggers, found $TRIGGER_COUNT" + fi + + echo "✓ Database migrations applied & verified" - name: Setup .NET SDK uses: actions/setup-dotnet@v4 diff --git a/.gitea/workflows/ci_lint.yml b/.gitea/workflows/ci_lint.yml index f4b89218..76118751 100644 --- a/.gitea/workflows/ci_lint.yml +++ b/.gitea/workflows/ci_lint.yml @@ -118,3 +118,30 @@ jobs: run: | python3 tools/validate_gitea_secrets_contract_v1.py echo "✓ Secrets contract validated" + + notify-results: + name: "Notify Lint Results" + if: always() + needs: [lint-workflows, validate-secrets-contract] + runs-on: ubuntu-latest + + steps: + - name: Report Workflow Validation Status + env: + LINT_STATUS: ${{ needs.lint-workflows.result }} + SECRETS_STATUS: ${{ needs.validate-secrets-contract.result }} + run: | + echo "════════════════════════════════════════════════════" + echo "Workflow Validation Report" + echo "════════════════════════════════════════════════════" + echo "" + echo "Lint & Structure: $([ "$LINT_STATUS" = "success" ] && echo "✅ PASS" || echo "❌ FAIL")" + echo "Secrets Contract: $([ "$SECRETS_STATUS" = "success" ] && echo "✅ PASS" || echo "❌ FAIL")" + echo "" + if [ "$LINT_STATUS" = "success" ] && [ "$SECRETS_STATUS" = "success" ]; then + echo "✅ All workflow validations passed" + exit 0 + else + echo "❌ Workflow validation failed — review logs above" + exit 1 + fi diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index 8967eeb8..819b187d 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -266,32 +266,38 @@ jobs: - name: Setup SSH run: | mkdir -p ~/.ssh + + # Priority: SSH_PRIVATE_KEY > DEPLOY_SSH_KEY_B64 > DEPLOY_SSH_KEY SSH_KEY="${{ secrets.SSH_PRIVATE_KEY }}" SSH_KEY_B64="${{ secrets.DEPLOY_SSH_KEY_B64 }}" SSH_KEY_RAW="${{ secrets.DEPLOY_SSH_KEY }}" - write_key() { - # $1 = raw secret value; auto-detects PEM vs base64 - if printf '%s' "$1" | grep -q 'BEGIN.*PRIVATE KEY'; then - printf '%b\n' "$1" > ~/.ssh/deploy_key - else - printf '%s' "$1" | base64 -d > ~/.ssh/deploy_key - fi - } - - if [ -n "$SSH_KEY" ]; then - write_key "$SSH_KEY" - elif [ -n "$SSH_KEY_B64" ]; then - printf '%s' "$SSH_KEY_B64" | base64 -d > ~/.ssh/deploy_key - elif [ -n "$SSH_KEY_RAW" ]; then - write_key "$SSH_KEY_RAW" - else + if [ -z "$SSH_KEY" ] && [ -z "$SSH_KEY_B64" ] && [ -z "$SSH_KEY_RAW" ]; then echo "ERROR: No SSH key configured" exit 1 fi - sed -i 's/\r$//' ~/.ssh/deploy_key - chmod 600 ~/.ssh/deploy_key + # Write SSH key (auto-detect PEM vs base64) + DEPLOY_KEY_PATH=~/.ssh/deploy_key + if [ -n "$SSH_KEY" ]; then + # SSH_PRIVATE_KEY is raw PEM or base64 + if printf '%s' "$SSH_KEY" | grep -q 'BEGIN.*PRIVATE KEY'; then + printf '%b\n' "$SSH_KEY" > "$DEPLOY_KEY_PATH" + else + printf '%s' "$SSH_KEY" | base64 -d > "$DEPLOY_KEY_PATH" + fi + elif [ -n "$SSH_KEY_B64" ]; then + printf '%s' "$SSH_KEY_B64" | base64 -d > "$DEPLOY_KEY_PATH" + elif [ -n "$SSH_KEY_RAW" ]; then + if printf '%s' "$SSH_KEY_RAW" | grep -q 'BEGIN.*PRIVATE KEY'; then + printf '%b\n' "$SSH_KEY_RAW" > "$DEPLOY_KEY_PATH" + else + printf '%s' "$SSH_KEY_RAW" | base64 -d > "$DEPLOY_KEY_PATH" + fi + fi + + sed -i 's/\r$//' "$DEPLOY_KEY_PATH" + chmod 600 "$DEPLOY_KEY_PATH" ssh-keyscan -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true echo "✓ SSH configured" @@ -384,30 +390,34 @@ jobs: - name: Setup SSH (reuse deploy credentials) run: | mkdir -p ~/.ssh + SSH_KEY="${{ secrets.SSH_PRIVATE_KEY }}" SSH_KEY_B64="${{ secrets.DEPLOY_SSH_KEY_B64 }}" SSH_KEY_RAW="${{ secrets.DEPLOY_SSH_KEY }}" - write_ssh_key() { - if printf '%s' "$1" | grep -q 'BEGIN.*PRIVATE KEY'; then - printf '%b\n' "$1" > ~/.ssh/deploy_key - else - printf '%s' "$1" | base64 -d > ~/.ssh/deploy_key - fi - } - - if [ -n "$SSH_KEY" ]; then - write_ssh_key "$SSH_KEY" - elif [ -n "$SSH_KEY_B64" ]; then - printf '%s' "$SSH_KEY_B64" | base64 -d > ~/.ssh/deploy_key - elif [ -n "$SSH_KEY_RAW" ]; then - write_ssh_key "$SSH_KEY_RAW" - else + if [ -z "$SSH_KEY" ] && [ -z "$SSH_KEY_B64" ] && [ -z "$SSH_KEY_RAW" ]; then echo "ERROR: No SSH key configured"; exit 1 fi - chmod 600 ~/.ssh/deploy_key 2>/dev/null || true - ssh-keyscan -p 22 ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true + DEPLOY_KEY_PATH=~/.ssh/deploy_key + if [ -n "$SSH_KEY" ]; then + if printf '%s' "$SSH_KEY" | grep -q 'BEGIN.*PRIVATE KEY'; then + printf '%b\n' "$SSH_KEY" > "$DEPLOY_KEY_PATH" + else + printf '%s' "$SSH_KEY" | base64 -d > "$DEPLOY_KEY_PATH" + fi + elif [ -n "$SSH_KEY_B64" ]; then + printf '%s' "$SSH_KEY_B64" | base64 -d > "$DEPLOY_KEY_PATH" + elif [ -n "$SSH_KEY_RAW" ]; then + if printf '%s' "$SSH_KEY_RAW" | grep -q 'BEGIN.*PRIVATE KEY'; then + printf '%b\n' "$SSH_KEY_RAW" > "$DEPLOY_KEY_PATH" + else + printf '%s' "$SSH_KEY_RAW" | base64 -d > "$DEPLOY_KEY_PATH" + fi + fi + + chmod 600 "$DEPLOY_KEY_PATH" 2>/dev/null || true + ssh-keyscan -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true echo "✓ SSH configured" - name: Health Check diff --git a/.gitea/workflows/kis_data_collection.yml b/.gitea/workflows/kis_data_collection.yml index 1bdae814..c6d1e5ac 100644 --- a/.gitea/workflows/kis_data_collection.yml +++ b/.gitea/workflows/kis_data_collection.yml @@ -89,10 +89,36 @@ jobs: python3 tools/validate_db_first_pipeline_v1.py echo "✓ Database schema pipeline validated" + validate-data-quality: + name: "Validate Daily Data Consistency" + runs-on: ubuntu-latest + env: + PYTHONPATH: "$HOME/python_deps/quality:." + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Python Environment + run: | + PYTHON_DEPS="$HOME/python_deps/quality" + mkdir -p "$PYTHON_DEPS" + /usr/bin/python3 -m pip install --disable-pip-version-check --quiet \ + --target "$PYTHON_DEPS" pyyaml + export PYTHONPATH="$PYTHON_DEPS:${PYTHONPATH:-}" + echo "PYTHONPATH=$PYTHON_DEPS:${PYTHONPATH:-}" >> "$GITHUB_ENV" + + - name: Run Daily Data Consistency Validation + run: | + mkdir -p Temp + python3 tools/validate_data_consistency_daily_v1.py --mode warn + echo "✓ Daily data consistency validation completed" + cat Temp/data_consistency_report.json | python3 -m json.tool + notify-status: name: "Notify Collection Status" if: always() - needs: [validate-credentials, validate-database-pipeline] + needs: [validate-credentials, validate-database-pipeline, validate-data-quality] runs-on: ubuntu-latest steps: @@ -100,6 +126,7 @@ jobs: env: CRED_STATUS: ${{ needs.validate-credentials.result }} DB_STATUS: ${{ needs.validate-database-pipeline.result }} + QUALITY_STATUS: ${{ needs.validate-data-quality.result }} run: | echo "═══════════════════════════════════════════════════════════" echo "KIS Data Collection & Validation Report" @@ -107,8 +134,9 @@ jobs: echo "" echo "Credentials Validation: $([ "$CRED_STATUS" = "success" ] && echo "✅ PASS" || echo "❌ FAIL")" echo "Database Pipeline: $([ "$DB_STATUS" = "success" ] && echo "✅ PASS" || echo "❌ FAIL")" + echo "Data Quality: $([ "$QUALITY_STATUS" = "success" ] && echo "✅ PASS" || echo "❌ FAIL")" echo "" - if [ "$CRED_STATUS" = "success" ] && [ "$DB_STATUS" = "success" ]; then + if [ "$CRED_STATUS" = "success" ] && [ "$DB_STATUS" = "success" ] && [ "$QUALITY_STATUS" = "success" ]; then echo "✅ All validations passed — KIS API is ready" exit 0 else diff --git a/.gitea/workflows/prepare-release.yml b/.gitea/workflows/prepare-release.yml index 2d64a6e3..02202fd8 100644 --- a/.gitea/workflows/prepare-release.yml +++ b/.gitea/workflows/prepare-release.yml @@ -188,8 +188,32 @@ jobs: encoding="utf-8", ) PY - echo "✓ Manifest created: ${ARTIFACT}.manifest.json" - cat "${ARTIFACT}.manifest.json" + echo "✓ Manifest created" + + - name: Validate Release Manifest + run: | + ARTIFACT="quantengine_${{ steps.metadata.outputs.version }}.tar.gz" + MANIFEST="${ARTIFACT}.manifest.json" + + python3 - <>'status' as status_change, + changed_at AT TIME ZONE 'UTC' as audit_time + FROM quantengine.kis_collection_runs_audit + WHERE changed_at > NOW() - INTERVAL '1 hour' + ORDER BY changed_at DESC; + EOF + +[ ] 3. 뷰를 통한 분석 확인 + psql -U quantengine_app -d quantenginedb < Metric: ... # 오직 completeness만 + +class FreshnessValidator: + def validate(self) -> Metric: ... # 오직 freshness만 + +class ConsistencyValidator: + def validate(self) -> Metric: ... # 오직 consistency만 + +class DataQualityValidator: + def __init__(self, validators: List[IValidator]): + self.validators = validators # Dependency Inversion + + def validate(self) -> DataQualityMetrics: + return DataQualityMetrics( + completeness=self.validators[0].validate(), + freshness=self.validators[1].validate(), + # ... + ) +``` + +### 데이터 정합성 (100% 감시 추적) +```sql +-- Audit trail: 모든 변경을 자동으로 기록 +CREATE TRIGGER kis_collection_runs_audit_trigger +AFTER INSERT OR UPDATE OR DELETE ON kis_collection_runs +FOR EACH ROW +EXECUTE FUNCTION kis_collection_runs_audit_trigger(); + +-- 결과: kis_collection_runs_audit 테이블에 +-- INSERT: changed_by='scheduler', action='INSERT', new_values={...} +-- UPDATE: changed_by='admin', action='UPDATE', old_values={...}, new_values={...} +-- DELETE: changed_by='maintenance', action='DELETE', old_values={...} +``` + +### 게임이론 (향후 Phase 2) +```python +# Nash Equilibrium 기반 포트폴리오 선택 +class GameTheoreticPortfolio: + def compute_nash_equilibrium(self, market_state: Dict) -> Allocation: + """ + Players: 포트폴리오 매니저들 + Strategy: 각 자산 비중 (0-1.0) + Payoff: Sharpe ratio + risk-adjusted return + + Goal: 다른 플레이어가 이탈할 유인이 없는 균형점 찾기 + """ + # Linear Programming으로 최적 비중 계산 + cov_matrix = self._compute_covariance(market_state) + expected_returns = self._compute_expected_returns(market_state) + + # 나의 risk aversion을 고려한 최적화 + optimal = self._solve_optimization( + cov_matrix, expected_returns, risk_aversion=self.lambda_ + ) + + return optimal +``` + +### 퀀트 엔진 데이터 기반 고도화 +```python +# Phase 2: 데이터 → 의사결정 파이프라인 +class QuantEngineDataFlow: + def run(self): + # 1. 데이터 수집 (kis_data_collection) + data = self.kis_collector.fetch_latest() # kis_collection_snapshots + + # 2. 데이터 검증 (validate_data_consistency_daily) + metrics = self.validator.validate(data) + if metrics.status != "PASS": + raise DataQualityError(f"Quality check failed: {metrics}") + + # 3. 정규화 (Phase 1: 3NF) + normalized = self.normalizer.normalize(data) # stocks/quotes/order_book + + # 4. 팩터 계산 (데이터 팩터 고도화) + factors = self.factor_engine.compute(normalized) + # factors = {sharpe_ratio, correlation, volatility, ...} + + # 5. 게임이론 기반 선택 (Phase 2) + portfolio = self.game_engine.compute_nash(factors) + + # 6. 의사결정 기록 (감시 추적) + self.decision_logger.log({ + "timestamp": now(), + "factors": factors, + "decision": portfolio, + "rationale": factors, # "왜"를 기록 + }) + + return portfolio +``` + +--- + +## 📊 성과 지표 최종 요약 + +### 8주 누적 체크리스트 +``` +Phase 0 Success Criteria: + +[ ] 1. CI Performance (Week 1) + ├─ Duration: 15-20min ✓ + ├─ Variance: <20% ✓ + └─ Reproducibility: 3 runs consistent ✓ + +[ ] 2. Data Audit Trail (Week 2) + ├─ kis_*_audit tables: 3 created ✓ + ├─ Triggers: 3 active ✓ + └─ Data captured: 100+ rows ✓ + +[ ] 3. Daily Validation (Week 3) + ├─ Automated checks: Running daily ✓ + ├─ Success rate: 100% ✓ + └─ 14 days data collected ✓ + +[ ] 4. Final Verification (Week 4-6) + ├─ CI reproducibility: 3 runs PASS ✓ + ├─ Deploy E2E: PASS ✓ + └─ Phase 1 Ready: YES ✓ + +PHASE 0: ✅ COMPLETE (Aug 31, 2026) +→ PHASE 1: 시작 (Sep 15, 2026) +``` + +--- + +**이 체크리스트를 매주 정리하면서 진행합니다! 🚀** diff --git a/WORKFLOW_AUDIT_REPORT.md b/WORKFLOW_AUDIT_REPORT.md new file mode 100644 index 00000000..0efe947a --- /dev/null +++ b/WORKFLOW_AUDIT_REPORT.md @@ -0,0 +1,326 @@ +# 워크플로우 감시 및 개선 보고서 (2026-07-24) + +## 🔍 전체 스캔 결과 + +### 파일별 상태 분석 + +| 파일명 | 상태 | 심각도 | 주요 이슈 | +|--------|------|--------|---------| +| `ci.yml` | ⚠️ 개선필요 | 중간 | Python 중복 설정, DOTNET_VERSION 오류 | +| `ci_lint.yml` | ✅ 양호 | 낮음 | job dependency 일관성 | +| `deploy-prod.yml` | ⚠️ 개선필요 | 높음 | SSH 코드 중복, 주석 과다 | +| `kis_data_collection.yml` | ⚠️ 개선필요 | 중간 | Daily validator 미통합 | +| `prepare-release.yml` | ✅ 양호 | 낮음 | 불필요한 echo 중복 | +| `qualitative_sell_strategy.yml` | ⚠️ 개선필요 | 중간 | pytest `|| true` 위험 | +| `snapshot_admin.yml` | ✅ 양호 | 낮음 | PYTHONPATH 일관성 | + +--- + +## 🐛 발견된 오류 및 개선사항 + +### 1. ci.yml + +**❌ 오류 1: DOTNET_VERSION 버전 지정 오류** +```yaml +# Line 15 +env: + DOTNET_VERSION: '10.0.x' # ← 오류: .NET 10.0은 존재하지 않음 +``` + +**수정:** +```yaml +env: + DOTNET_VERSION: '9.0.x' # ✓ 실제 존재하는 버전 +``` + +**❌ 오류 2: 중복된 Python 환경설정 (8회 반복)** +- core, wbs-audit, dotnet-contracts, ui-storage, database-schema, calibration-pipeline, operational-reporting, security-validation, workflow-lint +- 각 job마다 동일한 코드: `mkdir -p "$PYTHON_DEPS"`, `pip install`, `echo` +- 결과: ~100줄 낭비 + +**수정:** Composite action 또는 공유 setup script로 추출 + +**❌ 오류 3: PostgreSQL 마이그레이션 적용 후 트리거 검증 없음** +- V*.sql 파일 적용 후 `kis_*_audit` 테이블/트리거 실제 생성 확인 불가 +- 데이터베이스 오류가 조용하게 무시될 수 있음 + +**수정:** 마이그레이션 후 검증 쿼리 추가 +```bash +for f in $(ls src/dotnet/.../V*.sql | sort -V); do + psql ... -f "$f" +done +# ✓ 추가: 트리거 생성 확인 +psql -U quantengine_ci -d quantenginedb -c "SELECT COUNT(*) FROM information_schema.triggers WHERE trigger_schema='quantengine';" | grep -q "[0-9]" || exit 1 +``` + +--- + +### 2. ci_lint.yml + +**⚠️ 문제: `notify-results` job 없음** +- ci.yml의 다른 모든 job은 `notify-results`로 끝나지만, ci_lint.yml은 없음 +- 불일치 → 워크플로우 완료 알림 누락 + +**수정:** +```yaml +notify-results: + name: "Notify Lint Results" + if: always() + needs: [lint-workflows, validate-secrets-contract] + runs-on: ubuntu-latest + steps: + - name: Report Lint Status + run: | + LINT_STATUS=${{ needs.lint-workflows.result }} + SECRETS_STATUS=${{ needs.validate-secrets-contract.result }} + + if [ "$LINT_STATUS" = "success" ] && [ "$SECRETS_STATUS" = "success" ]; then + echo "✅ All workflow validations passed" + exit 0 + else + echo "❌ Workflow validation failed" + exit 1 + fi +``` + +--- + +### 3. deploy-prod.yml (높은 심각도) + +**❌ 오류 1: SSH key setup 코드 반복** +- Lines 266-296: setup-ssh in deploy job +- Lines 384-411: setup-ssh in post-deploy-check job +- **중복된 20줄 코드** + +**수정:** +```yaml +# ✓ 방법 1: Reusable composite action +# .github/actions/setup-ssh/action.yml +name: Setup SSH Deployment Key +runs: + using: composite + steps: + - run: | + mkdir -p ~/.ssh + SSH_KEY="${{ inputs.ssh_key }}" + SSH_KEY_B64="${{ inputs.ssh_key_b64 }}" + + write_key() { + if printf '%s' "$1" | grep -q 'BEGIN.*PRIVATE KEY'; then + printf '%b\n' "$1" > ~/.ssh/deploy_key + else + printf '%s' "$1" | base64 -d > ~/.ssh/deploy_key + fi + } + + [ -n "$SSH_KEY" ] && write_key "$SSH_KEY" || \ + [ -n "$SSH_KEY_B64" ] && printf '%s' "$SSH_KEY_B64" | base64 -d > ~/.ssh/deploy_key + + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -p 22 ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true +``` + +**❌ 오류 2: 주석 과다로 인한 가독성 저하** +- Line 315-327: 13줄 주석 +- Line 415-428: 14줄 주석 +- Line 468-480: 13줄 주석 + +**수정:** 주석 요약본 + 링크 형식 +```yaml +# 상세 문서: CLAUDE.md → "Local Development & Testing" +# 요약: SSH 계정 선택 우선순위: SSH_PRIVATE_KEY > DEPLOY_SSH_KEY_B64 > DEPLOY_SSH_KEY +``` + +**❌ 오류 3: 헬스 체크에서 DB 검증 로직 복잡** +- Line 481: `grep -c` 패턴이 복잡함 +- `|| echo "0"` 사용으로 "0\n0" 발생 가능 (실제로 발생했었음) + +**이미 수정됨** (Line 481에 `|| true` 사용) + +--- + +### 4. kis_data_collection.yml (중간 심각도) + +**❌ 오류 1: Daily validator 미통합** +- Phase 0에서 `validate_data_consistency_daily_v1.py` 구현됨 +- kis_data_collection.yml에는 아직 통합되지 않음 + +**수정:** +```yaml +validate-data-quality: + name: "Validate Daily Data Consistency" + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Python & PostgreSQL Client + run: | + PYTHON_DEPS="$HOME/python_deps/quality" + mkdir -p "$PYTHON_DEPS" + /usr/bin/python3 -m pip install --disable-pip-version-check --quiet \ + --target "$PYTHON_DEPS" psycopg2-binary pyyaml + export PYTHONPATH="$PYTHON_DEPS:${PYTHONPATH:-}" + echo "PYTHONPATH=$PYTHON_DEPS:${PYTHONPATH:-}" >> "$GITHUB_ENV" + + - name: "Run Daily Data Consistency Check" + env: + # SSH 터널로 원격 DB 접속: localhost:5432 → production DB + DB_CONNECTION: "postgresql://quantengine_app:quantengine_app@localhost:5432/quantenginedb" + run: | + python3 tools/validate_data_consistency_daily_v1.py --mode warn +``` + +**❌ 오류 2: outputs 변수 선언 후 미사용** +```yaml +# Line 22-23: 선언 +outputs: + mock-valid: ${{ steps.mock.outcome }} + prod-valid: ${{ steps.prod.outcome }} + +# Line 101: 사용하지 않음 (notify-status에서 needs.validate-credentials.outputs를 참조하지 않음) +``` + +**수정:** +```yaml +notify-status: + needs: [validate-credentials, validate-database-pipeline, validate-data-quality] + # ... + env: + MOCK_VALID: ${{ needs.validate-credentials.outputs.mock-valid }} + PROD_VALID: ${{ needs.validate-credentials.outputs.prod-valid }} + run: | + echo "Mock credentials: $MOCK_VALID" + echo "Prod credentials: $PROD_VALID" +``` + +--- + +### 5. prepare-release.yml + +**⚠️ 문제 1: 불필요한 echo 반복** +```yaml +# Lines 158-160: Package Artifact +echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT # ✓ 필요 +echo "✓ Package: $(du -sh $ARTIFACT | cut -f1)" # 이미 위에서 표시됨 +file "$ARTIFACT" # 너무 자세함 +``` + +**수정:** 간결하게 +```yaml +- name: Package Artifact + run: | + VERSION="${{ steps.metadata.outputs.version }}" + ARTIFACT="quantengine_${VERSION}.tar.gz" + tar -czf "$ARTIFACT" -C ./publish . + echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT + echo "✓ Package created: $(du -sh $ARTIFACT | awk '{print $1}')" +``` + +**⚠️ 문제 2: 매니페스트 생성 후 검증 없음** +- 매니페스트 JSON 생성 후 유효성 검사 없음 +- 파일이 비어있거나 형식이 잘못되어도 통과 + +**수정:** +```yaml +- name: Validate Release Manifest + run: | + MANIFEST="${{ steps.metadata.outputs.artifact }}.manifest.json" + python3 -c " + import json + with open('$MANIFEST') as f: + data = json.load(f) + assert 'version' in data and data['version'] + assert 'commit' in data and data['commit'] + assert 'sha256' in data and data['sha256'] + print('✓ Manifest valid') + " +``` + +--- + +### 6. qualitative_sell_strategy.yml + +**❌ 오류 1: pytest 실패해도 무시됨** +```yaml +# Line 50 +python3 -m pytest tests/unit/test_qualitative_sell_strategy_store_v1.py -v || true +# ← || true는 실패를 pass로 변환함 +``` + +**수정:** 실패 시 작업 실패로 전환 +```yaml +- name: Validate Strategy Store (Integration) + run: | + python3 -m pytest tests/unit/test_qualitative_sell_strategy_store_v1.py -v \ + --tb=short \ + --no-header + continue-on-error: false # ← 명시적으로 설정 +``` + +--- + +### 7. snapshot_admin.yml + +**⚠️ 문제: PYTHONPATH 불일치** +```yaml +# Line 17 (validate-workflow) +env: + PYTHONPATH: "$HOME/python_deps/snapshot:." + +# Line 45 (validate-ui) +env: + PYTHONPATH: "$HOME/python_deps/ui:." +``` + +**수정:** 일관된 PATH +```yaml +jobs: + setup-python: + runs-on: ubuntu-latest + outputs: + python-path: ${{ steps.setup.outputs.path }} + steps: + - id: setup + run: | + PYTHON_DEPS="$HOME/python_deps/snapshot" + mkdir -p "$PYTHON_DEPS" + /usr/bin/python3 -m pip install --disable-pip-version-check --quiet \ + --target "$PYTHON_DEPS" pyyaml pytest + echo "path=$PYTHON_DEPS" >> $GITHUB_OUTPUT +``` + +--- + +## 📋 종합 개선 체크리스트 + +### Priority P0 (즉시 필요) +- [ ] ci.yml: DOTNET_VERSION 수정 (10.0.x → 9.0.x) +- [ ] deploy-prod.yml: SSH setup 코드 중복 제거 (20줄 → composite action) +- [ ] kis_data_collection.yml: Daily validator 통합 +- [ ] qualitative_sell_strategy.yml: pytest `|| true` 제거 + +### Priority P1 (주간 중) +- [ ] ci.yml: Python 환경설정 공유 스크립트로 추출 (60줄 → 10줄) +- [ ] ci_lint.yml: `notify-results` job 추가 +- [ ] prepare-release.yml: 매니페스트 검증 추가 +- [ ] deploy-prod.yml: 주석 요약본으로 정리 + +### Priority P2 (선택) +- [ ] 모든 job에 명시적 timeout 설정 +- [ ] 일관된 artifact naming convention +- [ ] 각 job 성공 기준 명시 + +--- + +## 🚀 다음 단계 + +**1단계 (30분)**: P0 오류 수정 (4개 파일) +**2단계 (1시간)**: P1 개선 (4개 파일) +**3단계 (로컬 테스트)**: 각 워크플로우 YAML 문법 검증 +**4단계 (푸시)**: main에 커밋 및 CI 실행 + +--- + +이 보고서는 **PHASE0_WEEKLY_EXECUTION_TRACKER.md**의 Week 1 일일 작업으로 통합 가능합니다.