fa0da0b159
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 12s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 26s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 13s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 11s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 1m56s
Adds docs/SESSION_HANDOFF_2026-07-30.md with what a future session needs that isn't derivable from git log: open threads (release DAG unverified end-to-end, engine_history/quantengine schema collision left as-is, OpenDART/KRX not yet wired into any workflow) and traps hit this session worth not repeating (archive-before-grepping tools/spec/tests, DbUp ordinal sort, runtime/ is tracked history not scratch space, two Pythons on this machine, entropy audit counting local build noise). Pointed to it from CLAUDE.md's Known Issues list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
453 lines
30 KiB
Markdown
453 lines
30 KiB
Markdown
# 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 Governance** — `AssertReadOnly()` 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](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](docs/DEPLOYMENT_RUNBOOK.md)
|
||
|
||
### ⚠️ CRITICAL: CI/CD-Only Deployment Mandate
|
||
|
||
**Rule**: ALL production deployments MUST go through Gitea Actions CI/CD
|
||
(`prepare-release.yml` → `deploy-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/login`이 `28P01 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.conf`의 `EnvironmentFile=` 지시자로 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](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](docs/UI_GUIDELINES.md)
|
||
|
||
## Development Commands
|
||
|
||
### .NET
|
||
```powershell
|
||
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)
|
||
```powershell
|
||
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)
|
||
```powershell
|
||
# 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`:
|
||
```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.env`의
|
||
`ConnectionStrings__DefaultConnection`을 참고할 것 — 이 문서의 예시 값을 그대로 복사해 쓰지 말 것.
|
||
|
||
### Collection pipeline testing
|
||
```powershell
|
||
$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
|
||
```powershell
|
||
npx playwright test
|
||
npx playwright test tests/e2e/complete-admin-flow.spec.ts
|
||
```
|
||
|
||
### Calibration & data tools
|
||
```powershell
|
||
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
|
||
```powershell
|
||
# 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](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](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](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](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 (V1–V8 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 V1–V8** — 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 화이트리스트, 문서 등) 그 참조부터 정리한 뒤 삭제한다.
|
||
- **2026-07-30 전략 감사 세션 인수인계**: 미해결 항목(release DAG 전체 미검증, `engine_history`/`quantengine` 스키마 중복, OpenDART/KRX 미배선)과 재발 방지 트랩(문서 archive 전 `tools/`·`spec/`·`tests/` 전수 검색 필수, DbUp 정렬 버그 수정 완료, `runtime/`은 이력 데이터라 스크래치 출력 금지 등) — [docs/SESSION_HANDOFF_2026-07-30.md](docs/SESSION_HANDOFF_2026-07-30.md) 참고.
|
||
|
||
### 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](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](docs/OMS_WMS_ERP_PLAYBOOK.md)
|