10 KiB
Plan - FRESHNESS
이 파일을 읽는 구현 에이전트에게
이 task는 구현 후 CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채워야 완료된다. 검증을 실행하고 active 파일은 그대로 둔 채 리뷰 준비 상태를 보고한다. 종결, archive, complete.log 작성은 code-review 전용이다. 선택된 SDD 결정 또는 Milestone 구현 잠금 > 결정 필요 항목이 막을 때만 review stub의 사용자 리뷰 요청을 채우고, 사용자에게 직접 질문하지 않는다.
배경
freshness-readiness Task는 collection freshness/readiness output이 monthly/daily/minute 상태를 구분해야 한다. 현재 freshness runner는 request timeframe으로 bar를 조회하지만 gap/duplicate/readiness expected list는 daily expected_yyyymmdd만 허용한다. 출력도 freshness step의 timeframe을 렌더링하지 않아 여러 timeframe 상태를 한 fixture에 담기 어렵다.
사용자 리뷰 요청 흐름
구현 중 직접 사용자 프롬프트를 띄우지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정만 agent-ops/skills/common/_templates/implementation-user-review-request-section.md 형식으로 active review stub의 사용자 리뷰 요청에 기록하고, code-review가 USER_REVIEW.md 작성 여부를 판단한다.
Roadmap Targets
- Milestone:
agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md - Task ids:
freshness-readiness: collection freshness와 readiness output이 월/일/분 timeframe별 latest/missing/gap/duplicate 상태를 구분한다.
- Completion mode: check-on-pass
Spec Targets
- SDD:
agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md - Acceptance scenarios:
S08: task=freshness-readiness; evidence=freshness/readiness fixture text/JSONL for monthly/daily/minute latest/missing/gap/duplicate
- Completion mode: spec-check-on-pass
분석 결과
읽은 파일
agent-test/local/rules.mdagent-test/local/operations-smoke.mdapps/cli/internal/operator/scenario.goapps/cli/internal/operator/scenario_test.goapps/cli/internal/operator/runner.goapps/cli/internal/operator/runner_market_test.goapps/cli/internal/operator/output.go
테스트 환경 규칙
- test_env:
local. agent-test/local/rules.md를 읽었고 CLI/operator fixture 변경은 local Go test로 검증한다.operations-smoke는 bin/go.work 중심이지만 앱 CLI 작업용 별도 profile이 없어 repository Go package test를 fallback으로 사용한다.- 최종 명령은
go test -count=1 ./apps/cli/internal/operator로 둔다.
테스트 커버리지 공백
apps/cli/internal/operator/scenario.go:763-766은expected_yyyymmdd를 daily 외 timeframe에서 거부한다.apps/cli/internal/operator/runner.go:1321-1448은 timestamp를YYYYMMDD로만 bucket 처리한다.apps/cli/internal/operator/output.go:186-190은timeframe을 matrix output에서만 렌더링하고 freshness output에는 포함하지 않는다.- committed freshness fixtures는
market_data_status_query와us_market_data_status_querydaily만 있다.
심볼 참조
- rename/remove 없음. 새 YAML field와 StepEvent field를 추가하되 기존
expected_yyyymmdd,latest_yyyymmdd,gap_yyyymmdd는 compatibility로 유지한다.
분할 판단
- split policy를 먼저 평가했다.
- shared task group:
m-backtest-multi-timeframe-coverage. - sibling plans:
01_fill_policy: domain/worker backtest 체결 정책.02_freshness_readiness: operator freshness/readiness schema/output/fixture.
- 이 plan은
02_...형식이며01_...에 의존하지 않는다. predecessorcomplete.log가 없다.
범위 결정 근거
- proto/API contract는 변경하지 않는다.
collection_freshness는 기존ListInstruments/ListBars조합으로 계산한다. - provider import 성공 여부나 실제 market calendar는 다루지 않는다. fixture는 fake API bars로 stable evidence를 만든다.
- 기존 daily fixtures는 compatibility evidence로 유지하되, multi-timeframe readiness fixture를 별도로 추가한다.
빌드 등급
local-G07: operator schema/runner/output/fixtures를 함께 바꾸지만 앱 CLI package test로 deterministic 검증 가능하다.
구현 체크리스트
apps/cli/internal/operator/scenario.go에 timeframe-aware expected bucket schema를 추가하고 dailyexpected_yyyymmddcompatibility를 유지한다.apps/cli/internal/operator/runner.go가 monthly/daily/minute bucket으로 latest/missing/gap/duplicate/provider_delay를 계산하게 만든다.apps/cli/internal/operator/output.go가 freshness/readiness step에timeframe과 generic latest/gap/duplicate bucket fields를 text/JSONL로 출력하게 한다.apps/cli/internal/operator/scenario_test.go와runner_market_test.go에 monthly/daily/minute_1/minute_5 validation/runner tests를 추가한다.apps/cli/testdata/operator에 multi-timeframe freshness/readiness YAML과 expected JSONL fixture를 추가하고 runner/fixture validation tests에 evidence path를 연결한다. 검증: monthly/daily/minute freshness fixture가 stable text/JSONL로 구분된다.- 최종 검증 명령을 실행한다.
- CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
[FRESHNESS-1] Timeframe-Aware Expected Buckets
문제
apps/cli/internal/operator/scenario.go:763-766이 expected_yyyymmdd를 daily에만 허용해 monthly/minute gap/duplicate readiness를 표현할 수 없다.
763 if len(step.Request.ExpectedYYYYMMDD) > 0 {
764 if step.Request.Timeframe != "daily" {
765 return fmt.Errorf("step %q: expected_yyyymmdd is only supported for timeframe \"daily\"", step.ID)
해결 방법
Request.ExpectedBuckets []string yaml:"expected_buckets"와 Expect.LatestBucket string yaml:"latest_bucket"을 추가한다. format은 timeframe별로 검증한다: monthly=YYYYMM, daily=YYYYMMDD, minute_1/minute_5=YYYYMMDDHHMM. daily expected_yyyymmdd는 기존 fixtures용으로 유지하고 내부에서 daily expected buckets로 normalize한다.
수정 파일 및 체크리스트
apps/cli/internal/operator/scenario.go: schema field, validation, helper 추가.apps/cli/internal/operator/scenario_test.go: monthly/minute valid/invalid/duplicate/out-of-window tests 추가.
테스트 작성
- 작성:
TestValidateExpectedBuckets.
중간 검증
go test -count=1 ./apps/cli/internal/operator -run 'TestValidateExpected(Buckets|YYYYMMDD)'
기대: exit 0.
[FRESHNESS-2] Bucketed Freshness Runner
문제
apps/cli/internal/operator/runner.go:1321-1448은 모든 bar timestamp를 YYYYMMDD로 집계한다. minute duplicate와 monthly gap을 같은 날짜 단위로 뭉개므로 S08을 증명할 수 없다.
1322 t := time.UnixMilli(bar.GetTimestampUnixMs()).UTC()
1323 d := t.Format("20060102")
1324 dateCounts[d]++
해결 방법
runner에 freshnessBucket(tf, unixMs)와 normalized expected buckets를 둔다. latest bucket은 max timestamp의 bucket으로 계산하고, gap/duplicate/provider delay는 bucket index 기준으로 계산한다. daily compatibility output은 기존 latest_yyyymmdd/gap_yyyymmdd도 계속 채운다.
수정 파일 및 체크리스트
apps/cli/internal/operator/runner.go: bucket map 계산, expectation comparison 추가.apps/cli/internal/operator/runner_market_test.go: monthly gap, minute duplicate, minute delay tests 추가.
테스트 작성
- 작성:
TestRunCollectionFreshnessBucketsByTimeframe.
중간 검증
go test -count=1 ./apps/cli/internal/operator -run 'TestRunCollectionFreshness(BucketsByTimeframe|ZeroBarRegression)'
기대: exit 0.
[FRESHNESS-3] Stable Output And Fixture Evidence
문제
apps/cli/internal/operator/output.go:186-190은 timeframe을 matrix events에만 출력하고, output.go:233-255의 freshness block에는 timeframe/generic bucket keys가 없다. Existing freshness fixtures are daily only.
186 if ev.MatrixRunID != "" {
189 fields["timeframe"] = ev.Timeframe
해결 방법
freshness events에 timeframe, latest_bucket, gap_buckets, duplicate_buckets를 text/JSONL로 출력한다. 새 fixture multi_timeframe_freshness_readiness.yaml와 expected/multi_timeframe_freshness_readiness.jsonl을 추가해 monthly, daily, minute_1, minute_5 lines를 모두 포함한다. runner_market_test.go가 새 fixture path와 JSONL field evidence를 검증하게 한다.
수정 파일 및 체크리스트
apps/cli/internal/operator/output.go: StepEvent fields와 text/JSONL rendering.apps/cli/testdata/operator/multi_timeframe_freshness_readiness.yaml: 새 fixture.apps/cli/testdata/operator/expected/multi_timeframe_freshness_readiness.jsonl: 새 expected output.apps/cli/internal/operator/runner_market_test.go: 새 fixture JSONL validity/path 검증.
테스트 작성
- 작성:
TestMultiTimeframeFreshnessReadinessFixture.
중간 검증
go test -count=1 ./apps/cli/internal/operator -run 'Test(MultiTimeframeFreshnessReadinessFixture|ExpectedOutputFixturesAreValidJSONLines)'
기대: exit 0.
수정 파일 요약
| 파일 | 항목 |
|---|---|
apps/cli/internal/operator/scenario.go |
FRESHNESS-1 |
apps/cli/internal/operator/scenario_test.go |
FRESHNESS-1 |
apps/cli/internal/operator/runner.go |
FRESHNESS-2 |
apps/cli/internal/operator/runner_market_test.go |
FRESHNESS-2, FRESHNESS-3 |
apps/cli/internal/operator/output.go |
FRESHNESS-3 |
apps/cli/testdata/operator/*.yaml |
FRESHNESS-3 |
apps/cli/testdata/operator/expected/*.jsonl |
FRESHNESS-3 |
최종 검증
go test -count=1 ./apps/cli/internal/operator
기대: exit 0, new multi-timeframe freshness/readiness fixture and output tests pass.
모든 코드 변경 완료 후 반드시 CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.