Files
QuantEngineByItz/CLAUDE.md
T
kjh2064 477bd693c1 fix: unify OpenDART env var name with Gitea Secrets; add missed migration header notes
tools/ingest_fundamental_raw.py read DART_API_KEY, but the Gitea Secret
is registered as OPENDART_OPENAPI_KEY, and no workflow bridges the two
(none currently invoke this script). Renamed the code side to match
the secret name directly rather than adding a mapping layer, so
whenever this gets wired into a workflow it just works. Updated the
matching README setup instructions.

Also includes the V9/V10 migration header explanations (why they were
renamed from V003/V004) that were written earlier but missed from the
previous commit's file list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 11:39:48 +09:00

30 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

High-Level Architecture Overview

QuantEngine v0.1 — A hybrid quantitative analysis and data collection system for retirement asset portfolio management, transitioning from Python (legacy) to .NET 9 (primary).

System Architecture Layers

┌─────────────────────────────────────────────────────────────┐
│  Web UI Layer (Razor Pages)                                  │
│  ├─ Admin Dashboard, Users, Collection, Monitoring, Ops     │
│  └─ Server-side rendering + Cookie auth (no WASM)           │
├─────────────────────────────────────────────────────────────┤
│  .NET Web API Layer (FastEndpoints, Minimal APIs)            │
│  ├─ Collection API: /api/collection/{state,runs,snapshots}   │
│  ├─ Emergency password reset, auth endpoints                 │
│  └─ Portfolio/settings endpoints                             │
├─────────────────────────────────────────────────────────────┤
│  Application Layer (QuantEngine.Application)                 │
│  ├─ ~22 orchestrators/services between Web and Infrastructure│
│  ├─ KisDataCollectionOrchestrator, PipelineOrchestrator,     │
│  │  WorkspaceService, etc.                                   │
│  └─ Owns cross-cutting workflows; Web/Pages call these, not  │
│     Infrastructure directly                                  │
├─────────────────────────────────────────────────────────────┤
│  Repository Layer (Dapper ORM, SQL-driven)                   │
│  ├─ IWorkspaceRepository: workspace/settings CRUD            │
│  ├─ ICollectionRepository: KIS runs, snapshots, errors       │
│  ├─ IPortfolioRepository: asset/position data                │
│  └─ Whitelist-only table access (enum-based security)        │
├─────────────────────────────────────────────────────────────┤
│  Infrastructure Layer (.NET)                                 │
│  ├─ DbUp Migrations: src/dotnet/QuantEngine.Infrastructure/  │
│  ├─ PostgreSQL connection pooling, Npgsql 8.0                │
│  ├─ ITokenCache (KIS token management)                       │
│  ├─ PostProcessing: JSON normalization, schema validation    │
│  └─ Contracts: Parity, Provenance, Scheduling, Normalization│
├─────────────────────────────────────────────────────────────┤
│  KIS API Client Layer (.NET)                                 │
│  ├─ IKisApiClient: GetCurrentPrice, AskingPrice, DailyChart  │
│  ├─ Read-only mandate: quotations + ranking only             │
│  ├─ Governance enforcement: AssertReadOnly(path, trId)       │
│  ├─ Fallback chain: KIS → Naver → Yahoo → OpenDART          │
│  └─ Token caching: PostgreSQL-backed with 10-min skew        │
├─────────────────────────────────────────────────────────────┤
│  Data Collection Pipeline (Python legacy + .NET new)         │
│  ├─ Python (ops:data-collect): SQLite accumulator            │
│  │  └─ tools/run_kis_data_collection_v1.py                   │
│  ├─ .NET: Async collection runner, PostgreSQL persistence    │
│  ├─ Snapshot Admin (web UI): settings/account_snapshot edit   │
│  └─ Calibration Backlog: priority + change ledger            │
├─────────────────────────────────────────────────────────────┤
│  Decision Engine & Reporting (.NET Canonical Renderer)       │
│  ├─ QuantEngine.Tools: Final decision packet + report render │
│  ├─ JSON schema: schemas/operational_report.schema.json      │
│  ├─ Outputs: Temp/operational_report.{json,md}              │
│  └─ Contract validation via canonical JSON                   │
├─────────────────────────────────────────────────────────────┤
│  Database Layer (PostgreSQL)                                 │
│  ├─ Unified schema: quantenginedb / quantengine schema       │
│  ├─ Key tables: kis_tokens, kis_collection_runs/snapshots    │
│  │            workspaces, account_snapshots, settings        │
│  ├─ Auto-migrations via DbUp (V1__Name.sql, V2__Name.sql...) │
│  └─ Documented in: docs/db/quantengine.dbml (DBML sync)      │
├─────────────────────────────────────────────────────────────┤
│  Validation & Quality Gates (Python)                         │
│  ├─ 30+ validators: specs, contracts, WBS, audits            │
│  ├─ CI pipeline: 9 parallel jobs (core + 8 independent)      │
│  └─ Release gates: full-gate (strict), ops:release (warn-ok) │
├─────────────────────────────────────────────────────────────┤
│  CI/CD Orchestration (Gitea Actions)                         │
│  ├─ ci.yml: 10 jobs, core + contracts + reports + notify     │
│  ├─ prepare-release.yml: tag + artifact + checksum           │
│  ├─ deploy-prod.yml: SSH deploy + health checks              │
│  ├─ kis_data_collection.yml: cron (00:30 KST) validation     │
│  └─ Other workflows: snapshot admin, qualitative strategy    │
└─────────────────────────────────────────────────────────────┘

Key Design Decisions (SOLID + Domain-Driven)

  1. Razor Pages (Server-Rendered) — No WASM; Admin UI uses server-side rendering with cookie auth. Why? Simpler security, faster initial load, team familiarity.

  2. Repository Pattern + Dapper ORM — SQL-first, no EF. Why? Direct control over queries, performance, audit trail (raw SQL = explicit intent).

  3. Read-Only KIS GovernanceAssertReadOnly() blocks all /trading/ paths + TTTC*/VTTC* TR_IDs. Why? Hard mandate: data collection only, no order placement.

  4. PostgreSQL Single Source of Truth — All collection runs, snapshots, tokens centralized. Why? Audit, reproducibility, real-time coordination.

  5. Hybrid Python-to-.NET Transition — Python handles legacy ops:data-collect; .NET does web, API, orchestration. Why? Gradual migration reduces risk; Python stays until .NET collection fully validated.

  6. Contract-Driven Validation — Parity, Provenance, Scheduler, Normalization contracts in CI. Why? Catch schema drift, data quality regressions before deployment.

  7. Canonical JSON Renderer (.NET Tools) — Single source of operational_report.json schema. Why? One contract, all tools emit to it (no format wars).

Project Overview

QuantEngine v0.1 — A comprehensive quantitative analysis and data collection system for retirement asset portfolio management.

  • Architecture: .NET 9 + C# (web UI + APIs, primary); Python (legacy data collection, gradual deprecation)
  • Critical Path: KIS API → PostgreSQL → Admin Dashboard (read-only quotations only, no trading APIs)
  • Validation: 30+ contract validators + 9 parallel CI jobs + manual release gates
  • Web UI: Razor Pages (Server-Rendered) + Bootstrap 5/Tabler + Cookie auth (Blazor/MudBlazor deprecated 2026-07-11 — see UI Design Principles below)
  • Database: PostgreSQL (Npgsql 8.0), single unified database
  • Data Source: KIS Open API (quotations/ranking read-only), with fallbacks
  • Key Runtimes: .NET 9, Python 3.9+, Node.js 16+

Migration Status Summary

  • Phase 1 (Web UI → Razor Pages): Complete (2026-07-11)
  • Phase 2 (KIS Data Collection Pipeline): 95% complete — pipeline orchestration (Python→.NET) pending
  • Phase 3 (Node.js→.NET CLI Tools): Planned
  • Phase 4 (CI/CD Pipeline Hardening): 80% complete — Gitea Actions runner has network limitations, manual-trigger release workflow active (see Deployment & Operations)
  • Phase 5 (Admin UI & Deployment Optimization): Complete (2026-07-11)

Full phase-by-phase history and detail: docs/MIGRATION_STATUS.md

Deployment & Operations

Production Server: Hetzner Cloud 178.104.200.7 (kjh2064@178.104.200.7). Full runbook (release/deploy workflow, SSH key setup, health checks, rollback, troubleshooting): docs/DEPLOYMENT_RUNBOOK.md

⚠️ CRITICAL: CI/CD-Only Deployment Mandate

Rule: ALL production deployments MUST go through Gitea Actions CI/CD (prepare-release.ymldeploy-prod.yml, both manual workflow_dispatch). Manual SSH deployments are FORBIDDEN (audit trail, consistent health checks, rollback safety).

⚠️ CRITICAL: DB Secret Management (Incident 2026-07-12)

Incident: quant.taxbaik.com/login28P01 password authentication failed로 장애 발생 — appsettings.Production.json에 하드코딩되어 배포된 DB 비밀번호가 로테이션 이후에도 옛날 값 그대로 배포되고 있었음.

Rule: DB 접속 문자열(ConnectionStrings)은 절대 appsettings.Production.json이나 워크플로우 파일에 하드코딩하지 않는다. prepare-release.yml이 생성하는 아티팩트에는 Logging 설정만 있고 ConnectionStrings는 없다 (의도된 설계 — Gitea Release는 누구나 다운로드 가능하므로 시크릿을 담으면 안 됨).

실제 DB 비밀번호는 프로덕션 서버의 /home/kjh2064/.config/quantengine.env 파일에만 존재하며, quantengine.service.d/env.confEnvironmentFile= 지시자로 systemd가 환경변수로 주입한다 (환경변수가 appsettings.Production.json을 오버라이드).

DB 비밀번호 로테이션 시: /home/kjh2064/.config/quantengine.env만 갱신 + sudo systemctl restart quantengine. 워크플로우 파일/Gitea Secrets는 건드리지 않음.

배포 후 필수 확인: /Account/Login 실제 HTTP 응답 + journalctl -u quantengine에서 28P01/password authentication failed 부재 확인 (단순 active 상태만으로는 DB 연결 실패를 못 잡음 — ASP.NET Core는 DB 없이도 기동은 되고 로그인 요청 시점에야 실패함). 6-point 헬스체크에 DB 인증 확인이 포함되어 있음. 상세: docs/DEPLOYMENT_RUNBOOK.md

Git Repository

Gitea Server (동일 호스트):

  • HTTP: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz.git
  • SSH: ssh://git@gitea.taxbaik.com:2222/kjh2064/QuantEngineByItz.git

UI Design Principles

  • Primary Framework: ASP.NET Core Razor Pages (server-rendered, no WASM) + Bootstrap 5 + Tabler UI. Blazor Interactive WebAssembly, MudBlazor, SmartAdmin are deprecated (2026-07-11). The .sln has 6 projects: Core, Application, Infrastructure, Web, Tools, Core.Tests — there is no QuantEngine.Web.Client in the repo (fully removed, not just excluded from the solution; verified 2026-07-30).
  • Authentication: Cookie (QuantEngine.Admin.Auth, HttpOnly, SameSite=Lax, 12h sliding) + BCrypt + IP lockout (3 strikes/15min). Folder-level [Authorize] via AuthorizeFolder("/Admin") — no per-page repetition. Login: /Account/Login.
  • Component rules: PageModel constructor DI (repos/services directly, no API hop); OnPostAsync()/OnPostDeleteAsync() multi-handler pattern; validation failures return Page(); DataAnnotations + FluentValidation double-check.
  • Full component-to-Bootstrap/Tabler mapping table (buttons, inputs, tables, badges, modals, validation messages): docs/UI_GUIDELINES.md

Development Commands

.NET

cd src/dotnet
dotnet restore
dotnet build                                          # Debug (0 errors, 0 warnings)
dotnet build -c Release
dotnet watch run --project QuantEngine.Web            # Hot-reload → http://localhost:5265
dotnet test src/dotnet/QuantEngine.Core.Tests -c Release
dotnet test src/dotnet/QuantEngine.sln --configuration Release

Python / Node.js (legacy & release gates)

npm install
npm run ops:validate              # Warn-only validation
npm run full-gate                 # Strict validation (all gates PASS)
npm run ops:data-collect          # KIS collection (Python subprocess)
npm run ops:release               # Full release DAG
npm run prepare-upload-zip        # Package for distribution
python3 -m pytest tests/unit/test_*.py -v

Local run (SSH tunnel required — never use Docker locally)

# Terminal 1: tunnel to remote PostgreSQL
ssh -L 127.0.0.1:5432:localhost:5432 kjh2064@178.104.200.7 -N

# Terminal 2: run the app
cd src/dotnet
dotnet watch run --project QuantEngine.Web
# → http://localhost:5265/Account/Login

appsettings.Development.json:

{ "ConnectionStrings": { "DefaultConnection": "Host=127.0.0.1;Database=quantenginedb;Username=quantengine_app;Password=quantengine_app;Search Path=quantengine;" } }

⚠️ 2026-07-30 확인: 위 quantengine_app/quantengine_app 비밀번호로 실제 프로덕션 DB에 접속을 시도하면 password authentication failed로 거부된다 (직접 psql 연결 테스트로 확인). 로컬 개발 시 실제 값은 /home/kjh2064/.config/quantengine.envConnectionStrings__DefaultConnection을 참고할 것 — 이 문서의 예시 값을 그대로 복사해 쓰지 말 것.

Collection pipeline testing

$env:KIS_APP_Key_TEST = "your_kis_test_key"
$env:KIS_APP_Secret_TEST = "your_kis_test_secret"
dotnet run --project QuantEngine.Web
# → http://localhost:5265/collection — "Start Collection" triggers an async run

curl http://localhost:5265/api/collection/state
curl http://localhost:5265/api/collection/runs
curl "http://localhost:5265/api/collection/latest/005930"

E2E / Playwright

npx playwright test
npx playwright test tests/e2e/complete-admin-flow.spec.ts

Calibration & data tools

python tools/build_calibration_priority_v1.py
python tools/build_calibration_change_ledger_v4.py
python tools/build_calibration_review_report_v1.py
python tools/validate_calibration_change_ledger_v1.py
python tools/run_kis_data_collection_v1.py --input-json GatherTradingData.json --sqlite-db src/quant_engine/kis_data_collection.db --output-json Temp/kis_data_collection_v1.json --kis-account real
python tools/run_snapshot_admin_server_v1.py --host 127.0.0.1 --port 8787 --db src/quant_engine/snapshot_admin.db --seed GatherTradingData.json

CI/CD monitoring

# https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions
python3 tools/validate_gitea_ci_workflow_lint_v1.py

Full day-to-day scenarios (KIS credential validation, snapshot admin editing, release/deploy, CI debugging, DB schema changes, "when things break" table): docs/DEV_WORKFLOWS.md

API Endpoints

All endpoints prefixed with /api/. Full route tables (Workspace & History, Collection Pipeline) and Collection Run status/success-criteria definitions: docs/API_REFERENCE.md

KIS API Client Security (Phase 2)

Governance Enforcement

  • Read-Only Mandate: AssertReadOnly(path, trId) blocks all trading-related endpoints
  • Forbidden Paths: /trading/ substring triggers 🚫 immediate exception
  • Forbidden TR_IDs: TTTC* / VTTC* prefixes (buy/sell order codes) blocked
  • Source: governance/rules/06_no_direct_api_trading.yaml

Token Management

  • ITokenCache abstraction: PostgreSQL-backed in production
  • Credential Loading:
    • Windows environment variables: KIS_APP_Key, KIS_APP_Secret, KIS_APP_Key_TEST, KIS_APP_Secret_TEST
    • Fallback: HKCU\Environment registry (Windows only)
    • Account modes: "real" (prod) vs "mock" (sandbox)

Quotation Methods (All Read-Only)

  1. GetCurrentPriceAsync (FHKST01010100) — Current price inquiry
  2. GetAskingPrice10LevelAsync (FHKST01010200) — Order book (10-level)
  3. GetDailyShortSaleAsync (FHPST04830000) — Short-sale trends
  4. GetDailyItemChartPriceAsync (FHKST03010100) — Daily OHLCV data
  5. GetInvestorTrendAsync (FHKST01010900) — Investor sentiment (개인/외국인/기관)

OpenDART Fallback API (2026-07-30)

Credential: OPENDART_OPENAPI_KEY 환경변수 — Gitea Secrets에 이미 이 이름으로 등록되어 있음(사용자 확인, 2026-07-30). 코드(tools/ingest_fundamental_raw.py:56, os.environ.get("OPENDART_OPENAPI_KEY"))가 읽는 이름도 동일하게 통일했다 (원래 DART_API_KEY를 읽고 있어 Gitea Secrets 이름과 어긋났던 것을 2026-07-30에 수정 — 로컬 개발 시에도 OPENDART_OPENAPI_KEY로 설정할 것). 실제 키 값은 코드/문서/워크플로우 파일에 절대 하드코딩하지 않는다(KIS와 동일 원칙). 참고: 이 스크립트를 실제로 호출하는 CI 워크플로우는 아직 없다 — 나중에 워크플로우에 연결할 때 env: OPENDART_OPENAPI_KEY: ${{ secrets.OPENDART_OPENAPI_KEY }} 만 추가하면 됨(매핑 불필요, 이름이 이미 같음).

⚠️ CRITICAL: 일일 호출 한도 40,000건 — OpenDART는 계정당 하루 40,000건으로 제한된다. 이 한도를 넘으면 그날 나머지 호출이 전부 실패한다. DART를 호출하는 모든 코드(현재는 tools/ingest_fundamental_raw.py_dart_fundamentals() — 아직 스텁이며 실제 API 호출은 미구현)는 구현 시점부터 다음을 지켜야 한다:

  • 종목 수 × 종목당 호출 수(재무제표 조회, corpCode 매핑 등)가 하루 40,000건을 넘지 않도록 사전에 예산을 계산한다 (여유분 확보 — 재시도/오류 포함 실제 한도의 80% 이내 목표 권장).
  • corpCode.xml 매핑처럼 자주 바뀌지 않는 데이터는 반드시 캐싱한다 (기존 DART_CORP_MAP_CACHE가 7일 캐시로 이미 이 원칙을 따르고 있음 — 새 DART 호출도 동일하게 캐싱 우선으로 설계할 것).
  • 일일 호출 수를 실제로 추적하는 카운터가 아직 없다 — 실 데이터 호출을 구현할 때 호출 카운트를 파일 또는 DB에 기록하고 한도 근접 시 중단하는 가드를 함께 추가한다.

KRX Open API (2026-07-30, 미구현)

Credential: Gitea Secrets에 KRX_OPENAPI_KEY라는 이름으로 이미 등록되어 있음 (사용자 확인, 2026-07-30). 코드/문서/워크플로우 파일에 실제 값을 절대 하드코딩하지 않는다 (KIS와 동일 원칙) — 로컬 개발 시 환경변수로도 KRX_OPENAPI_KEY를 그대로 쓸 것(아직 이를 읽는 코드가 없으므로 새 이름 지어낼 필요 없음).

⚠️ 2026-07-30 확인: 저장소 전체에 KRX Open API를 실제로 호출하는 클라이언트 코드가 아직 존재하지 않는다 (KRX라는 단어 자체는 시장/거래소 명칭으로 여러 spec 파일에 등장하지만, API 키를 쓰는 실제 호출부는 없음 — grep으로 확인). .gitea/workflows/에도 KRX_OPENAPI_KEY를 참조하는 곳이 없다. 실제 구현 시:

  • KIS/OpenDART와 마찬가지로 일일/분당 호출 한도를 KRX 공식 문서에서 확인하고 호출 예산을 먼저 설계할 것 (한도를 여기 문서에 기록하지 않은 이유: 사용자로부터 아직 전달받지 않음 — 추측해서 적지 않는다).
  • 신규 클라이언트 추가 시 AssertReadOnly류 거버넌스 가드가 필요한지(주문 API 존재 여부) KRX 공식 문서로 먼저 확인.

Local Development & Testing

Mandatory Pre-Deployment Checklist

EVERY code change must pass before creating a release:

  1. Local build (0 errors, 0 warnings): dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release
  2. Local service starts cleanly via SSH tunnel (DbUp migrations succeed, no DB connection errors)
  3. Login test (/Account/Login → 200, cookie set)
  4. All Admin pages return 200 — Dashboard, Users, Collection, Monitoring, Operations (no 500 in response body)
  5. Playwright E2E: npx playwright test tests/e2e/complete-admin-flow.spec.ts
  6. git status clean, all changes pushed to main
  7. Authorization Policy configured (if changes made to Program.cs)

Deployment failure is better than service outage. Halt and investigate if any step fails. Full deployment runbook, SSH key setup, Gitea Actions API monitoring, secrets configuration: docs/DEPLOYMENT_RUNBOOK.md

Core Workflows & Common Scenarios

Day-to-day dev loop, KIS credential validation, snapshot admin editing, multi-stage release/deploy, CI debugging, DB schema change process, and a "when things break" troubleshooting table: docs/DEV_WORKFLOWS.md


Notes for Contributors (2026-07-11 — Updated 2026-07-24)

Code Standards

  • SQL Safety: Whitelist-only table access (enum switch in Repository). Never raw concatenation.
  • KIS API: Read-only quotations/ranking; no order/trade endpoints. Enforced via AssertReadOnly() in client.
  • Admin UI: Server-rendered Razor Pages only; no WASM. PageModel constructor: DI repos directly, no API hops.
  • Authentication: Cookie-based (HttpOnly, SameSite=Lax). No Bearer tokens. Password reset via API endpoints only (no UI form).
  • Password Hashing: BCrypt (auto-migrates from SHA-256 on login). IP lockout: 3 failed attempts → 15 min ban.
  • Database: PostgreSQL as single source of truth. Dapper ORM + raw SQL (no Entity Framework). Auto-migrations via DbUp.
  • Error Handling: Validate only at system boundaries (user input, external APIs). Trust internal guarantees.
  • Comments: Minimal; only explain WHY for non-obvious constraints. Removed comments on refactor; code names explain WHAT.

Repository Structure Rules

  • Legacy Code: No QuantEngine.Web.Client remains in the repo — fully removed (verified 2026-07-30), not just excluded from .sln.
  • DBML Sync (2026-07-12): All DbUp migrations (V*.sql) must sync with docs/db/quantengine.dbml in same commit. Future schema reads use DBML.
  • Migration naming/ordering (2026-07-30, fixed): V003_add_audit_trail_tables.sql and V004_normalize_snapshots_schema.sql used to sort before V1__Initial_Schema.sql under DbUp's default ordinal filename sort (zero-padded V003 < unpadded V1). Verified against production that neither had ever actually applied (absent from quantengine.schemaversions) — and investigation showed why: V004 has an unguarded FK to a table V2 creates, so on a fresh database it would hard-fail and abort every migration after it (V1V8 would never run at all); V003's trigger-creation guards would have silently no-op'd forever for the same reason. Fixed by renaming them to V9__Add_Audit_Trail_Tables.sql / V10__Normalize_Snapshots_Schema.sql (safe — never journaled anywhere) and adding MigrationScriptNameComparer (QuantEngine.Infrastructure/Data/MigrationScriptNameComparer.cs, wired into DbMigrator.cs via .WithScriptNameComparer(...)), which sorts scripts by numeric V{n} value instead of raw string order — so V10 correctly sorts after V9/V2/etc. regardless of digit count. This makes the whole scheme collision-proof going forward: new migrations can just use the next integer, V{n}__Name.sql, with no padding needed. Still do not rename V1V8 — those are already journaled in production, and DbUp identifies applied scripts by filename.
  • Diagrams: Mermaid diagrams in docs/diagrams/ for state machines, flows, sequences (e.g., collection-pipeline.md).
  • WBS Evidence (2026-07-12): Task completion = npm run verify:task -- <TASK_ID> PASS (BE=DB queries/logs/JSON, FE=Playwright+screenshot). Full check: npm run verify:wbs.

Testing & Validation

  • Local pre-deployment: Build (0 errors), E2E tests, all admin pages return 200 (no 500), git status clean.
  • CI auto-validation: 9 parallel jobs (core + 8 independent). Expected time: ~15-20min (was ~40min, parallelized 2026-07-24).
    • core: CRITICAL tests + DB setup
    • wbs-audit, dotnet-contracts, ui-storage, database-schema, calibration, operational-reporting, security-validation, workflow-lint: parallel
    • notify-results: final PR summary
  • Release gates: npm run ops:validate (warn-ok), npm run full-gate (strict), npm run ops:release (warn-ok + full DAG).

Deployment

  • Mandatory checklist: Local build, E2E pass, admin pages 200, git clean, pushed to main.
  • Release creation: Manual workflow_dispatch → prepare-release.yml (tag + artifact).
  • Deployment: Manual workflow_dispatch → deploy-prod.yml (SSH upload + health checks).
  • DB secrets: Never hardcode ConnectionStrings in artifacts. Runtime injection via systemd EnvironmentFile (production only).
  • Health checks (6-point): HTTP 200, login page, CSS load, service active, release tag match, DB auth (no 28P01 errors).

Known Issues & Tech Debt

  • Newtonsoft.Json: High-severity vuln (GHSA-5crp-9r3c-p9vr); update or replace when feasible.
  • Python-to-.NET transition: Legacy ops:data-collect remains until .NET collection validated in production (est. Q3 2026).
  • Gitea Actions limitation: Act runner ↔ Gitea network issues (workaround: manual SSH deployment active).
  • 도구 버전 관리 (2026-07-30): _v1/_v2... 접미사로 새 버전의 도구를 추가할 때, 이전 버전이 더 이상 쓰이지 않으면 삭제한다 — git 히스토리로 충분하다. 병행 존재 금지(예: apply_engine_upgrade_v4.py_v7.py가 동시에 남아있는 상태). 단, 다른 파일이 옛 버전을 이름으로 참조 중이면(lint 화이트리스트, 문서 등) 그 참조부터 정리한 뒤 삭제한다.

Reliability & Data Quality

  • Reproducibility: All validation gates, WBS tasks, and CI runs must be reproducible. Use deterministic inputs (GatherTradingData.json seed).
  • Audit trail: PostgreSQL is source of truth for all collection runs/snapshots. Raw SQL = explicit intent (no ORM magic).
  • Contract validation: Parity, Provenance, Scheduler, Normalization contracts catch schema drift before deployment.
  • Canonical rendering: .NET Tools (QuantEngine.Tools) is single source for operational_report.json schema. No format divergence.

When Making Changes

  1. Change type determines scope:

    • Bug fix: No cleanup beyond fix itself.
    • Feature: Add only what's requested; no hypothetical abstractions.
    • Refactor: Break down large methods/classes; extract reusable patterns only if used 3+ times.
    • Architecture change: Must update CLAUDE.md and CI contracts simultaneously.
  2. Locality-first: Prefer editing in place. Create new files only when genuinely new concept or isolation needed.

  3. Testing locality: Unit tests verify code correctness; UI testing (Playwright) verifies feature correctness. If UI can't be tested, say so.

  4. Git commits: New commits preferred over amend. Include co-author: Claude Haiku 4.5 <noreply@anthropic.com>.

CI/CD Pipeline Structure

9-job parallel ci.yml pipeline (~15-20min, was single-job ~40min before 2026-07-24 refactor). core job blocks downstream (DB migrations, .NET/API tests); 7 jobs run in parallel (wbs-audit, dotnet-contracts, ui-storage, database-schema, calibration-pipeline→ operational-reporting, security-validation, workflow-lint); notify-results summarizes all jobs in the PR comment.

Other workflows: kis_data_collection.yml (cron 00:30 KST M-F), qualitative_sell_strategy.yml (cron 00:15 KST M-F), ci_lint.yml (on push to .gitea/workflows/), snapshot_admin.yml, ci-frontend.yml (push/PR — 8-step src/frontend/ pipeline: typecheck, lint, unit, contract parity, build, E2E, audit), t20_ledger.yml (cron 17:00 KST M-F — builds the T+20 outcome ledger), prepare-release.yml (manual), deploy-prod.yml (manual).

Full job dependency graph, per-job purpose table, trigger schedule, workflow maintenance checklist, troubleshooting: docs/CICD_PIPELINE.md


OMS·WMS·ERP Commercialization Project (Phase 12)

Enterprise Order/Warehouse/ERP platform commercialization, gated by QuantEngine Phase 2 (KIS integration) completion in production. 18 weeks, 12 phases, 13 FTE, $371K budget, target launch Q4 2026. Phase 0 (Requirements & Baseline) complete 2026-07-26 — GO decision for Phase 1 (Dev Environment & CI/CD), starting 2026-08-02.

Ground-truth specs: spec/60_oms_wms_erp_wbs.yaml (WBS), spec/61_strategic_execution_framework.yaml (30 principles), spec/63_oms_wms_erp_api_openapi.yaml (OpenAPI), spec/64_oms_wms_erp_database_schema.sql (DB schema), spec/65_adr_001_monolithic_spa_architecture.md (ADR), spec/66_component_taxonomy.md (component taxonomy).

Full 30-principle framework, 12-phase breakdown, Phase 1-4 dev guide (Vite/Storybook setup, component code templates, Pinia stores, Playwright E2E), team/budget/risk detail, Phase 1 Go/No-Go checklist: docs/OMS_WMS_ERP_PLAYBOOK.md