<!-- task=m-live-trading-boundary/05+02,03,04_audit_trail plan=0 tag=LIVE_AUDIT -->

# Plan - LIVE_AUDIT

## 이 파일을 읽는 구현 에이전트에게

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것은 필수다. 구현 후 검증을 실행하고 실제 변경 내용, 설계 결정, 명령 출력(stdout/stderr)을 기록한 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 판정, 로그 rename, `complete.log`, archive 이동은 code-review 전용이다.

구현 중 사용자만 결정할 수 있는 사항, 사용자 소유 외부 환경/secret, 또는 계획 범위 충돌 없이는 안전하게 진행할 수 없으면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정, 근거, 실행 명령/출력, 재개 조건을 기록하고 중단한다. 구현 에이전트는 사용자에게 직접 묻거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

Live order, risk, account sync가 생긴 뒤에는 operator confirmation, risk decision, broker response, account sync를 재현 가능한 audit trail로 남겨야 한다. 기존 worker storage는 market/backtest 중심이고 `services/worker/internal/storage/ports.go:16`부터 instrument/bar/backtest ports만 있다. 이 계획은 live operation audit trail을 durable PostgreSQL storage와 headless query surface로 추가한다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식을 따른다. 구현 중 직접 사용자 프롬프트는 금지이며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/trading-expansion/milestones/live-trading-boundary.md`
- Task ids:
  - `audit-trail`: live operation audit trail이 있다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/trading-expansion/PHASE.md`
- `agent-roadmap/phase/trading-expansion/milestones/live-trading-boundary.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/domain-model/rules.md`
- `agent-ops/rules/project/domain/contracts/rules.md`
- `agent-ops/rules/project/domain/worker/rules.md`
- `agent-ops/rules/project/domain/api/rules.md`
- `agent-ops/rules/project/domain/operations/rules.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/domain-model-smoke.md`
- `agent-test/local/contracts-smoke.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/api-smoke.md`
- `agent-test/local/client-smoke.md`
- `agent-test/local/operations-smoke.md`
- `packages/domain/backtest/types.go`
- `packages/domain/market/types.go`
- `packages/contracts/proto/alt/v1/paper_trading.proto`
- `bin/contracts-gen`
- `bin/contracts-check`
- `bin/worker-storage-gen`
- `bin/worker-storage-check`
- `bin/test`
- `bin/lint`
- `services/worker/internal/storage/ports.go`
- `services/worker/internal/storage/postgres/store.go`
- `services/worker/internal/storage/postgres/mapping.go`
- `services/worker/internal/storage/postgres/mapping_test.go`
- `services/worker/internal/storage/postgres/migrate.go`
- `services/worker/internal/storage/postgres/migrate_test.go`
- `services/worker/internal/storage/postgres/store_test.go`
- `services/worker/internal/storage/postgres/queries/queries.sql`
- `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql`
- `services/worker/internal/storage/postgres/migrations/000002_backtest_analysis_results.up.sql`
- `services/worker/internal/storage/postgres/migrations/000003_instrument_asset_type.up.sql`
- `services/worker/internal/storage/postgres/migrations/000003_instrument_asset_type.down.sql`
- `services/worker/sqlc.yaml`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/handlers.go`
- `services/worker/internal/socket/paper.go`
- `services/worker/internal/socket/paper_test.go`
- `services/api/internal/workerclient/client.go`
- `services/api/internal/socket/handlers.go`
- `services/api/internal/socket/paper.go`
- `services/api/internal/socket/paper_test.go`
- `apps/cli/internal/operator/scenario.go`
- `apps/cli/internal/operator/runner.go`
- `apps/cli/internal/operator/client.go`
- `apps/cli/internal/operator/client_test.go`
- `apps/cli/internal/operator/output.go`
- `apps/cli/testdata/operator/headless_validation.md`

### 테스트 환경 규칙

`test_env=local`이다. contracts/worker/api/client/operations smoke profile을 적용한다. Storage migration/SQL query behavior가 바뀌므로 `bin/worker-storage-check` 실행 결과 또는 차단 사유가 필수다. `.proto`가 바뀌면 `bin/contracts-gen`, `bin/contracts-check`, `go test ./packages/contracts/gen/go/...`, `bin/lint`를 실행한다. Worker/API/CLI tests는 각각 `go test ./services/worker/...`, `go test ./services/api/...`, `go test ./apps/cli/...`다.

### 테스트 커버리지 공백

- Durable live audit storage: 기존 store ports와 migrations에는 live audit table/query가 없다.
- Event emission: 기존 paper/live boundary는 audit writer가 없다. Submit/cancel/risk/account sync event tests가 필요하다.
- Audit query API/CLI: 기존 CLI handoff matrix에는 live audit row가 없다.
- Secret/raw payload safety: audit payload가 raw token, account number, provider raw response를 저장하지 않는지 테스트가 필요하다.

### 심볼 참조

Renamed/removed symbols: none. 새 storage interfaces, sqlc queries, live audit messages/actions만 추가한다.

### 분할 판단

공통 task group은 `m-live-trading-boundary`다. sibling split은 `01_broker_port`, `02+01_order_lifecycle`, `03+01,02_risk_kill_switch`, `04+01_account_sync`, `05+02,03,04_audit_trail`이다. 이 subtask는 predecessor `02`, `03`, `04`가 필요하다. 현재 active completion logs는 없으므로 구현 시작 전 order lifecycle, risk/kill switch, account sync PASS가 필요하다.

### 범위 결정 근거

이 subtask는 live order/risk/account behavior 자체를 새로 설계하지 않고, 완료된 predecessor의 events를 기록하고 조회한다. KIS network order/account adapter 구현은 제외한다. Audit payload는 sanitized JSON만 허용하며 raw secrets, raw tokens, raw account numbers, provider raw bodies는 저장하지 않는다.

### 빌드 등급

Build lane `cloud-G07`, review lane `cloud-G07`. Storage migration, sqlc generated output, protocol/API/CLI query surface, security-sensitive audit payload boundary가 함께 있어 cloud review가 필요하다.

## 의존 관계 및 구현 순서

Directory dependency source of truth: `05+02,03,04_audit_trail` means predecessor indices `02`, `03`, `04`. `02+01_order_lifecycle`, `03+01,02_risk_kill_switch`, `04+01_account_sync`의 `complete.log`가 생긴 뒤 구현한다.

## 구현 체크리스트

- [ ] [LIVE_AUDIT-1] live audit domain/contract event shape를 확장한다.
- [ ] [LIVE_AUDIT-2] storage port, migration, sqlc query, postgres store를 추가한다.
- [ ] [LIVE_AUDIT-3] worker live operations에 audit writer와 query handler를 연결한다.
- [ ] [LIVE_AUDIT-4] API/CLI audit query workflow와 fixtures를 추가한다.
- [ ] [LIVE_AUDIT-5] storage/generated/focused verification을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [LIVE_AUDIT-1] Audit Event Shape

#### 문제

`packages/contracts/proto/alt/v1/paper_trading.proto`는 paper state/order response만 있고 operator confirmation, risk decision, broker response, account sync event를 시간순으로 조회할 audit event가 없다.

#### 해결 방법

`packages/domain/trading`에 `AuditEvent`와 `AuditEventType`을 확장한다. `live_trading.proto`에 `LiveAuditEvent`, `ListLiveAuditEventsRequest/Response`를 추가한다. Payload는 `map<string,string>` 또는 sanitized JSON string 중 하나로 정하되, raw provider body를 저장하지 않는다는 테스트를 추가한다.

Before:

```go
// services/worker/internal/storage/ports.go:45
type BacktestAnalysisStore interface {
	ListRuns(ctx context.Context, status backtest.RunStatus) ([]backtest.Run, error)
	GetRunDetail(ctx context.Context, id backtest.RunID) (RunDetail, error)
	CompareResults(ctx context.Context, ids []backtest.RunID) ([]backtest.Result, error)
}
```

After:

```go
type LiveAuditStore interface {
	AppendLiveAuditEvent(ctx context.Context, event trading.AuditEvent) error
	ListLiveAuditEvents(ctx context.Context, filter trading.AuditFilter) ([]trading.AuditEvent, error)
}
```

#### 수정 파일 및 체크리스트

- [ ] `packages/domain/trading/audit.go`와 `audit_test.go`를 추가한다.
- [ ] `packages/contracts/proto/alt/v1/live_trading.proto`를 확장한다.
- [ ] `bin/contracts-gen`/generated output을 갱신한다.
- [ ] parser maps에 audit messages를 등록한다.

#### 테스트 작성

작성한다. Event type stability, sanitized payload, filter defaults를 검증한다.

#### 중간 검증

```bash
bin/contracts-gen
bin/contracts-check
go test ./packages/domain/... ./packages/contracts/gen/go/...
```

기대 결과: exit code 0.

### [LIVE_AUDIT-2] Storage

#### 문제

현재 migrations는 instrument/bar/backtest tables만 만든다(`000001_worker_backbone.up.sql:1`, `:23`, `:35`). `queries.sql:1`부터 instrument/backtest queries만 있고 live audit query가 없다. `ports.go:16`부터 storage interface에도 audit store가 없다.

#### 해결 방법

`000004_live_audit_events.up.sql`과 `.down.sql`을 추가한다. Table은 `event_id`, `account_id`, `broker`, `order_id`, `event_type`, `status`, `reason`, `actor`, `correlation_id`, `payload`, `created_at`를 갖고 account/order/time index를 둔다. `queries.sql`에 append/list queries를 추가하고 `bin/worker-storage-gen`으로 sqlc output을 갱신한다. `Store`가 `storage.LiveAuditStore`를 구현한다.

#### 수정 파일 및 체크리스트

- [ ] `services/worker/internal/storage/ports.go`에 `LiveAuditStore`를 추가한다.
- [ ] `services/worker/internal/storage/postgres/migrations/000004_live_audit_events.up.sql`을 추가한다.
- [ ] `services/worker/internal/storage/postgres/migrations/000004_live_audit_events.down.sql`을 추가한다.
- [ ] `services/worker/internal/storage/postgres/queries/queries.sql`에 audit queries를 추가한다.
- [ ] `services/worker/internal/storage/postgres/mapping.go`에 audit mapping을 추가한다.
- [ ] `services/worker/internal/storage/postgres/store.go`에 append/list methods를 추가한다.
- [ ] `services/worker/internal/storage/postgres/sqlc/**`는 `bin/worker-storage-gen`으로만 갱신한다.

#### 테스트 작성

작성한다. `migrate_test.go`에 000004 up/down pairing과 table columns check를 추가한다. `mapping_test.go`에 audit mapping round trip과 raw secret key rejection을 추가한다. `store_test.go`에 append/list order/filter integration test를 추가한다.

#### 중간 검증

```bash
bin/worker-storage-gen
bin/worker-storage-check
go test ./services/worker/internal/storage/...
```

기대 결과: exit code 0. PostgreSQL-backed tests skip 조건은 기존 `store_test.go:27`의 `DATABASE_URL` 기준을 따른다. `bin/worker-storage-check` 실행 불가 시 pass가 아니며 사유를 기록한다.

### [LIVE_AUDIT-3] Worker Emission And Query

#### 문제

Live order/risk/account services가 operation event를 기록할 writer dependency가 없다. Audit query handler도 없다.

#### 해결 방법

`livetrading.Service`에 optional `AuditStore` 또는 `AuditWriter`를 주입한다. Submit confirmation, broker accepted/rejected, cancel, risk blocked, kill switch set, account sync success/failure에 audit event를 append한다. Append failure는 operation response에 internal error로 반영할지, operation success와 audit failure를 분리할지 명확히 정한다. 이 boundary milestone에서는 audit trail 필수성을 위해 append failure는 typed internal error로 처리한다.

#### 수정 파일 및 체크리스트

- [ ] `services/worker/internal/livetrading/service.go`에 audit writer를 연결한다.
- [ ] `services/worker/internal/livetrading/service_test.go`에 event emission tests를 추가한다.
- [ ] `services/worker/internal/socket/live.go`에 `ListLiveAuditEvents` handler를 추가한다.
- [ ] `services/worker/internal/socket/live_mapping.go`에 audit proto mapping을 추가한다.
- [ ] `services/worker/internal/socket/live_test.go`에 audit query tests를 추가한다.

#### 테스트 작성

작성한다. Submit, risk blocked, cancel, account sync event가 expected order로 append되는지, audit writer failure가 internal error로 매핑되는지 검증한다.

#### 중간 검증

```bash
go test ./services/worker/internal/livetrading ./services/worker/internal/socket
```

기대 결과: exit code 0.

### [LIVE_AUDIT-4] API And CLI Query

#### 문제

Operator가 audit trail을 headless로 조회할 API/CLI surface가 없다.

#### 해결 방법

API workerclient와 socket handler에 `ListLiveAuditEvents` forwarder를 추가한다. CLI에는 `list_live_audit_events` action을 추가하고 output에 `audit_count`, `audit_event_id`, `audit_event_type`, `audit_order_id`, `audit_status`, `audit_reason`을 render한다. `apps/cli/testdata/operator/live_audit_trail.yaml`과 expected JSONL을 추가하고 handoff matrix에 row를 추가한다.

#### 수정 파일 및 체크리스트

- [ ] `services/api/internal/workerclient/client.go`를 확장한다.
- [ ] `services/api/internal/socket/live.go`와 tests를 확장한다.
- [ ] `apps/cli/internal/operator/scenario.go`, `runner.go`, `client.go`, `output.go`를 확장한다.
- [ ] `apps/cli/internal/operator/runner_live_test.go`에 audit tests를 추가한다.
- [ ] `apps/cli/testdata/operator/live_audit_trail.yaml`과 expected JSONL을 추가한다.
- [ ] `apps/cli/testdata/operator/headless_validation.md`에 audit row를 추가한다.

#### 테스트 작성

작성한다. CLI list success, filter by account/order, typed not_found or empty list behavior, fixture JSONL parse를 포함한다.

#### 중간 검증

```bash
go test ./services/api/internal/socket ./apps/cli/internal/operator
```

기대 결과: exit code 0.

### [LIVE_AUDIT-5] Verification

#### 문제

Audit trail은 storage migration/generated drift와 cross-module proto/API/CLI drift를 동시에 일으킨다.

#### 해결 방법

Storage, contracts, affected Go modules, generated Dart lint까지 검증한다.

#### 수정 파일 및 체크리스트

- [ ] `bin/worker-storage-check`를 실행한다.
- [ ] `bin/contracts-check`를 실행한다.
- [ ] `go test ./packages/domain/...`를 실행한다.
- [ ] `go test ./packages/contracts/gen/go/...`를 실행한다.
- [ ] `go test ./services/worker/...`를 실행한다.
- [ ] `go test ./services/api/...`를 실행한다.
- [ ] `go test ./apps/cli/...`를 실행한다.
- [ ] `bin/lint`를 실행한다.

#### 테스트 작성

추가 테스트는 LIVE_AUDIT-1부터 LIVE_AUDIT-4에 포함한다.

#### 중간 검증

```bash
bin/worker-storage-check
bin/contracts-check
go test ./packages/domain/...
go test ./packages/contracts/gen/go/...
go test ./services/worker/...
go test ./services/api/...
go test ./apps/cli/...
bin/lint
```

기대 결과: 모두 exit code 0. `bin/worker-storage-check` 또는 `bin/lint`를 실행할 수 없으면 pass가 아니며 `command -v` 근거와 미실행 사유를 기록한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/domain/trading/audit.go` | LIVE_AUDIT-1 |
| `packages/domain/trading/audit_test.go` | LIVE_AUDIT-1 |
| `packages/contracts/proto/alt/v1/live_trading.proto` | LIVE_AUDIT-1 |
| `services/worker/internal/contracts/parser_map.go` | LIVE_AUDIT-1 |
| `services/api/internal/contracts/parser_map.go` | LIVE_AUDIT-1 |
| `apps/cli/internal/operator/parser_map.go` | LIVE_AUDIT-1 |
| `services/worker/internal/storage/ports.go` | LIVE_AUDIT-2 |
| `services/worker/internal/storage/postgres/migrations/000004_live_audit_events.up.sql` | LIVE_AUDIT-2 |
| `services/worker/internal/storage/postgres/migrations/000004_live_audit_events.down.sql` | LIVE_AUDIT-2 |
| `services/worker/internal/storage/postgres/queries/queries.sql` | LIVE_AUDIT-2 |
| `services/worker/internal/storage/postgres/mapping.go` | LIVE_AUDIT-2 |
| `services/worker/internal/storage/postgres/mapping_test.go` | LIVE_AUDIT-2 |
| `services/worker/internal/storage/postgres/migrate_test.go` | LIVE_AUDIT-2 |
| `services/worker/internal/storage/postgres/store.go` | LIVE_AUDIT-2 |
| `services/worker/internal/storage/postgres/store_test.go` | LIVE_AUDIT-2 |
| `services/worker/internal/storage/postgres/sqlc/**` | LIVE_AUDIT-2 |
| `services/worker/internal/livetrading/service.go` | LIVE_AUDIT-3 |
| `services/worker/internal/livetrading/service_test.go` | LIVE_AUDIT-3 |
| `services/worker/internal/socket/live.go` | LIVE_AUDIT-3 |
| `services/worker/internal/socket/live_mapping.go` | LIVE_AUDIT-3 |
| `services/worker/internal/socket/live_test.go` | LIVE_AUDIT-3 |
| `services/api/internal/workerclient/client.go` | LIVE_AUDIT-4 |
| `services/api/internal/socket/live.go` | LIVE_AUDIT-4 |
| `services/api/internal/socket/live_test.go` | LIVE_AUDIT-4 |
| `apps/cli/internal/operator/scenario.go` | LIVE_AUDIT-4 |
| `apps/cli/internal/operator/runner.go` | LIVE_AUDIT-4 |
| `apps/cli/internal/operator/client.go` | LIVE_AUDIT-4 |
| `apps/cli/internal/operator/output.go` | LIVE_AUDIT-4 |
| `apps/cli/internal/operator/runner_live_test.go` | LIVE_AUDIT-4 |
| `apps/cli/testdata/operator/live_audit_trail.yaml` | LIVE_AUDIT-4 |
| `apps/cli/testdata/operator/expected/live_audit_trail.jsonl` | LIVE_AUDIT-4 |
| `apps/cli/testdata/operator/headless_validation.md` | LIVE_AUDIT-4 |

## 최종 검증

```bash
bin/worker-storage-check
bin/contracts-check
go test ./packages/domain/...
go test ./packages/contracts/gen/go/...
go test ./services/worker/...
go test ./services/api/...
go test ./apps/cli/...
bin/lint
```

기대 결과: 모든 명령 exit code 0. `bin/worker-storage-check` 실행 불가 시 `command -v go`와 실패 stdout/stderr를 기록한다. `bin/lint`가 Flutter/Dart toolchain 부재로 실행 불가하면 `command -v flutter` 결과와 함께 차단 근거를 기록한다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
