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>
30 KiB
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)
-
Razor Pages (Server-Rendered) — No WASM; Admin UI uses server-side rendering with cookie auth. Why? Simpler security, faster initial load, team familiarity.
-
Repository Pattern + Dapper ORM — SQL-first, no EF. Why? Direct control over queries, performance, audit trail (raw SQL = explicit intent).
-
Read-Only KIS Governance —
AssertReadOnly()blocks all /trading/ paths + TTTC*/VTTC* TR_IDs. Why? Hard mandate: data collection only, no order placement. -
PostgreSQL Single Source of Truth — All collection runs, snapshots, tokens centralized. Why? Audit, reproducibility, real-time coordination.
-
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.
-
Contract-Driven Validation — Parity, Provenance, Scheduler, Normalization contracts in CI. Why? Catch schema drift, data quality regressions before deployment.
-
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.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
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
.slnhas 6 projects: Core, Application, Infrastructure, Web, Tools, Core.Tests — there is noQuantEngine.Web.Clientin 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]viaAuthorizeFolder("/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 returnPage(); 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.env의
ConnectionStrings__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\Environmentregistry (Windows only) - Account modes:
"real"(prod) vs"mock"(sandbox)
- Windows environment variables:
Quotation Methods (All Read-Only)
- GetCurrentPriceAsync (FHKST01010100) — Current price inquiry
- GetAskingPrice10LevelAsync (FHKST01010200) — Order book (10-level)
- GetDailyShortSaleAsync (FHPST04830000) — Short-sale trends
- GetDailyItemChartPriceAsync (FHKST03010100) — Daily OHLCV data
- 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:
- ✅ Local build (0 errors, 0 warnings):
dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release - ✅ Local service starts cleanly via SSH tunnel (DbUp migrations succeed, no DB connection errors)
- ✅ Login test (
/Account/Login→ 200, cookie set) - ✅ All Admin pages return 200 — Dashboard, Users, Collection, Monitoring, Operations (no 500 in response body)
- ✅ Playwright E2E:
npx playwright test tests/e2e/complete-admin-flow.spec.ts - ✅
git statusclean, all changes pushed to main - ✅ 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.Clientremains 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 withdocs/db/quantengine.dbmlin same commit. Future schema reads use DBML. - Migration naming/ordering (2026-07-30, fixed):
V003_add_audit_trail_tables.sqlandV004_normalize_snapshots_schema.sqlused to sort beforeV1__Initial_Schema.sqlunder DbUp's default ordinal filename sort (zero-paddedV003< unpaddedV1). Verified against production that neither had ever actually applied (absent fromquantengine.schemaversions) — and investigation showed why:V004has an unguarded FK to a tableV2creates, 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 toV9__Add_Audit_Trail_Tables.sql/V10__Normalize_Snapshots_Schema.sql(safe — never journaled anywhere) and addingMigrationScriptNameComparer(QuantEngine.Infrastructure/Data/MigrationScriptNameComparer.cs, wired intoDbMigrator.csvia.WithScriptNameComparer(...)), which sorts scripts by numericV{n}value instead of raw string order — soV10correctly sorts afterV9/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
ConnectionStringsin 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-collectremains 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
-
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.
-
Locality-first: Prefer editing in place. Create new files only when genuinely new concept or isolation needed.
-
Testing locality: Unit tests verify code correctness; UI testing (Playwright) verifies feature correctness. If UI can't be tested, say so.
-
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