fix: migration ordering bug that could block fresh-DB deploys
V004_normalize_snapshots_schema.sql had an unguarded FK to a table V2
creates. Under DbUp's default ordinal filename sort, the zero-padded
"V003_"/"V004_" migrations sorted before "V1__", so on a brand-new
database V004 would hard-fail on that FK and abort every migration
after it - V1 through V8 would never run. Confirmed via production
that neither V003 nor V004 had ever actually applied.
Fix: renamed them to V9__/V10__ and added MigrationScriptNameComparer,
which sorts DbUp scripts by numeric V{n} value instead of raw string
order, so double-digit versions can never again sort ahead of earlier
single-digit ones. Added regression tests for both the fixed case and
the original bug shape.
Also updates docs/db/quantengine.dbml (renamed migrations, and closes
out the engine_history/quantengine table-name-collision question -
both schemas are live, backing different code paths, not duplicates)
and CLAUDE.md (migration ordering fix, KIS/OpenDART/KRX credential
env-var-name reference, including a CI secret/env-var name mismatch
found for OpenDART that still needs a decision).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -286,9 +286,16 @@ Pipeline) and Collection Run status/success-criteria definitions:
|
||||
|
||||
## OpenDART Fallback API (2026-07-30)
|
||||
|
||||
**Credential**: `DART_API_KEY` 환경변수 (KIS 키와 동일한 패턴 — 코드/문서/워크플로우 파일에
|
||||
절대 하드코딩하지 않는다. 실제 키 값은 로컬 환경변수 또는 Gitea Secrets에만 존재해야 한다).
|
||||
로딩 위치: `tools/ingest_fundamental_raw.py:55` (`os.environ.get("DART_API_KEY")`).
|
||||
**Credential**: Gitea Secrets에는 `OPENDART_OPENAPI_KEY`라는 이름으로 이미 등록되어 있음
|
||||
(사용자 확인, 2026-07-30). **⚠️ 이름 불일치 주의**: 실제 코드(`tools/ingest_fundamental_raw.py:55`,
|
||||
`os.environ.get("DART_API_KEY")`)는 `DART_API_KEY`라는 *다른* 환경변수명을 읽는다. `.gitea/workflows/`
|
||||
전체를 확인했지만 `OPENDART_OPENAPI_KEY` → `DART_API_KEY` 매핑이 어떤 워크플로우에도 없다 —
|
||||
즉 지금 이 스크립트를 CI에서 돌리면 Gitea Secrets에 값이 있어도 코드가 못 읽는다. 다음 중
|
||||
하나로 해소해야 함(사람 판단 필요, 여기서는 실행하지 않음):
|
||||
(a) 워크플로우 step에 `env: DART_API_KEY: ${{ secrets.OPENDART_OPENAPI_KEY }}` 매핑 추가, 또는
|
||||
(b) 코드가 읽는 환경변수명을 `OPENDART_OPENAPI_KEY`로 통일.
|
||||
로컬 개발 시에는 이름 불일치와 무관하게 `DART_API_KEY`로 설정해야 현재 코드가 그대로 동작한다.
|
||||
실제 키 값은 코드/문서/워크플로우 파일에 절대 하드코딩하지 않는다(KIS와 동일 원칙).
|
||||
|
||||
**⚠️ CRITICAL: 일일 호출 한도 40,000건** — OpenDART는 계정당 하루 40,000건으로 제한된다.
|
||||
이 한도를 넘으면 그날 나머지 호출이 전부 실패한다. DART를 호출하는 모든 코드(현재는
|
||||
@@ -302,6 +309,23 @@ Pipeline) and Collection Run status/success-criteria definitions:
|
||||
- 일일 호출 수를 실제로 추적하는 카운터가 아직 없다 — 실 데이터 호출을 구현할 때 호출
|
||||
카운트를 파일 또는 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
|
||||
@@ -344,7 +368,7 @@ troubleshooting table: [docs/DEV_WORKFLOWS.md](docs/DEV_WORKFLOWS.md)
|
||||
|
||||
- **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 (2026-07-30)**: Existing migrations mix two conventions — `V1__Name.sql` (double underscore, unpadded) and `V003_name.sql` (single underscore, zero-padded). DbUp sorts scripts alphabetically, so `V003_...` actually runs *before* `V1__...`. **Do not rename existing migration files** — DbUp journals applied scripts by filename/checksum, so a rename makes an already-applied migration look new and re-runs it. New migrations must use `V{next_number}__Name.sql` (double underscore, matching V1–V8, the majority convention) so filename order matches intended execution order. **Note**: verified against production (`quantengine.schemaversions`, 2026-07-30) that `V003`/`V004` have never actually run — only V1–V8 are journaled. Renumbering V003/V004 to `V9__`/`V10__` is therefore safe from a checksum standpoint, but changes their execution order relative to V1–V8 (they currently run *first*); that's a real design decision (open, not made here), not a blind rename.
|
||||
- **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`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user