Critical re-review of the QuantEngine WBS evidence system found several
regressions of the "no fake gates" discipline established by M0, plus a
still-unwired M1 collection path. This closes 10 more WBS tasks
(QE-M1-01..06, QE-M2-01/02/04/05/06 — see spec/60_quant_engine_wbs.yaml)
with real, gate-verified evidence (18/34 total).
M1 — real KIS data now lands in PostgreSQL end-to-end:
- SchedulerService: load ticker universe from GatherTradingData.json instead
of a hardcoded array; fix a Hangfire scoped-service resolution bug.
- KisDataCollectionOrchestrator: restore logging on the lineage-event write
path (was a bare `catch {}` swallowing all failures silently); persist
daily OHLCV bars into quantengine.price_history_daily per run.
- Verified live: POST /api/collection/run -> Hangfire -> orchestrator ->
KIS mock API -> PostgreSQL, with Playwright DOM/API parity evidence.
M2 — historical price-history pipeline:
- CollectionRepository: SavePriceHistoryDailyAsync (idempotent upsert),
GetPriceHistorySummaryAsync (per-ticker aggregation) + a new
DateOnlyTypeHandler registered globally, since Dapper has no built-in
System.DateOnly support in either direction (write threw
NotSupportedException, read threw a constructor-mismatch
InvalidOperationException — found by exercising both paths live).
- tools/validate_price_history_integrity_v1.py: gap-freeness (vs KIS
trading calendar) + price-sanity gate over collected history.
- Admin Collection page: new "히스토리 현황" summary table +
GET /api/collection/history-summary, with Playwright evidence.
Governance/gate fixes:
- validate_market_time_series_schema_v1.py mislabeled its own output
"runtime_database_query": "DATA_GATED" despite never opening a DB
connection (pure file/regex check) — relabeled "check_scope":
"STATIC_STRUCTURAL_ONLY" and wired the node into the release DAG so it
isn't only reachable from ci.yml, matching every other validator.
Live-data authority for the same claim stays with QE-M2-01's pg_query
gate (spec/60), documented in spec/64.
- Fixed a WBS log_pattern check (QE-M1-06) that couldn't match its own
multi-line target; loosened two depends_on edges (QE-M1-05/06,
QE-M2-04/05) that encoded "needs X verified" when the real requirement
was only "needs X's code merged."
- Discovered and fixed admin-pages.spec.ts logging in with the wrong
seeded password (admin/admin instead of admin/quant123!, per CLAUDE.md)
— every test in that suite had been silently failing at the login step.
Deferred: QE-M2-03 (2-year backfill) — the KIS mock/VTS token endpoint
started returning 403 after the first successful call this session; looks
like a token-issuance rate limit or credential issue on KIS's side, not a
code defect. Backfilling at scale right now would just generate more 403s,
so left QE-M2-03 PENDING pending KIS account/console verification.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
User caught this directly: it's already 2026-07-12 in Korea, but
Run #2008's release was tagged quant_20260711.3.7150737 -- the wrong
date.
Confirmed: UTC was still 2026-07-11 16:2x when KST was already
2026-07-12 01:2x (9-hour offset). prepare-release.yml computed
TODAY via `TZ=UTC date +%Y%m%d`, which is only "correct" if the team
operates on UTC -- but this project's production server logs,
deployment cadence, and team are all Korea Standard Time. Any release
cut between midnight and 9am KST would silently tag itself with
yesterday's date.
Fixed by using `TZ=Asia/Seoul date +%Y%m%d` instead.
Per spec: the sequence number is a per-day counter that resets on
date change and starts at 0, not 1. The first release of a day is
quant_YYYYMMDD.0.hash, the second .1, etc.
Previous commit fixed *counting* today's releases via the Gitea API
(instead of the always-empty local git tags from a shallow checkout),
but still added +1 on top, which would have produced 1, 2, 3... for
the first, second, third releases of a day instead of 0, 1, 2.
DEPLOY_COUNT is now just RELEASES_TODAY directly.
User asked why every release tonight had the same "sequence number"
(quant_20260711.1.*) despite creating three of them. Confirmed via
API: tags b7591fb, 6ab270f, and e49922e all exist for 2026-07-11, all
claiming to be deploy #1.
Root cause: `actions/checkout@v4` (no fetch-depth/fetch-tags options)
does a shallow, tags-less clone by default. Each prepare-release.yml
run happens in a brand-new container, so `git tag -l "quant_${TODAY}.*"`
always sees zero local tags regardless of how many releases actually
exist -- DEPLOY_COUNT was permanently stuck at 0+1=1.
Fix: query GET /repos/{repo}/tags via the Gitea API (same token/curl
pattern already used elsewhere in this workflow) to count today's
actual tags, instead of relying on the job's local, incomplete git
state.