<!-- task=02+yaml_validation plan=2 tag=REVIEW_REVIEW_REFACTOR -->

# YAML Validation Workflow Key Follow-up

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

**경고: 구현 완료의 마지막 단계는 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 모두 채우는 것이다.**
구현 체크리스트를 기준으로 작업하고, plan과 review stub 양쪽의 체크리스트를 완료하며, 중간/최종 검증을 실행한 실제 출력으로 `CODE_REVIEW-*-G??.md`를 채운다.
리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시는 실행하지 말고, `코드리뷰 전용 체크리스트`는 수정하거나 체크하지 않는다.

## 배경

Review 1에서 root/property/command id/type 검증은 통과했지만, `pipeline.workflow` node의 key가 String이 아닌 경우 YAML 변환 단계의 `jsonEncode()`가 먼저 실패하는 문제가 확인됐다.
이 케이스는 REFACTOR-3의 workflow node shape guard가 담당해야 하는 invalid workflow 구조이므로, raw encoder 예외 대신 validation message로 흘러야 한다.

## 분석 결과

### 읽은 파일

- `lib/oto/application.dart`
- `lib/oto/pipeline/pipeline.dart`
- `test/oto_application_test.dart`
- `test/oto_core_test.dart`
- `agent-task/02+yaml_validation/code_review_cloud_G07_1.log`

### 리뷰 실패 근거

다음 smoke에서 raw encoder error가 재현됐다.

```yaml
commands:
- command: Print
  id: hello
  param:
    message: hi
pipeline:
  id: main
  workflow:
  - 123: hello
```

현재 결과:

```text
Converting object to an encodable object failed: Instance of 'YamlMap'
```

기대 방향:

- `workflow[0]` 위치가 드러나는 validation message로 실패한다.
- generated parser, JSON encoder, Dart cast 예외가 사용자에게 직접 노출되지 않는다.

### 빌드 등급

- build lane: `cloud-G07`. 반복 Required이고 YAML schema/CLI smoke 검증 신뢰도 이슈이므로 기존 cloud-G07을 유지한다.

## 구현 체크리스트

- [ ] nested `YamlMap`/`YamlList` 변환이 non-string workflow key에서 raw `jsonEncode` 예외를 내지 않게 한다.
- [ ] non-string workflow task key가 `workflow[index]`를 포함한 validation message로 실패하게 한다.
- [ ] non-string workflow task key 테스트와 기존 최종 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REFACTOR-1] nested YAML map 변환 안전화

#### 문제

`lib/oto/application.dart:260`의 `getMapFromYamlA()`는 root가 `YamlMap`인지 확인한 뒤 `jsonEncode(data)`로 전체 YAML을 JSON 변환한다.
하지만 nested `YamlMap`에 non-string key가 있으면 JSON encoder가 `JsonUnsupportedObjectError`를 던지고, 이후 `_validateBuildMap()`/`Pipeline.pipelineInitialize()`가 실행되지 않는다.

#### 해결 방법

`jsonEncode(data)`에 의존하기보다 `YamlMap`/`YamlList`를 재귀적으로 plain Dart `Map<String, dynamic>`/`List<dynamic>`로 변환한다.
Map key는 String으로 변환하거나, 변환하지 않는다면 위치 정보를 포함한 validation error로 반환한다.
workflow task key를 String으로 변환하는 방식을 택하면 `Pipeline.pipelineInitialize()`가 `"123"`을 unsupported task로 판정해 기존 지원 task 목록 메시지를 재사용할 수 있다.

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

- [ ] `lib/oto/application.dart`: nested `YamlMap`/`YamlList`를 안전하게 plain Dart 구조로 변환하는 helper를 추가한다.
- [ ] `lib/oto/application.dart`: `getMapFromYamlA()`가 non-string nested key에서 raw encoder 예외를 던지지 않게 한다.

#### 테스트 작성

- `test/oto_application_test.dart` 또는 `test/oto_core_test.dart`에 workflow node key가 숫자인 YAML을 추가한다.

#### 중간 검증

```bash
dart test test/oto_application_test.dart test/oto_core_test.dart
```

기대 결과: 신규 invalid workflow key 테스트와 기존 Application/Core 테스트가 모두 통과한다.

### [REVIEW_REVIEW_REFACTOR-2] workflow key validation message 고정

#### 문제

workflow node key가 non-string인 경우에도 사용자에게는 어느 workflow item이 잘못됐는지 보여야 한다.
현재 raw encoder error에는 `workflow[0]`도 지원 task 목록도 없다.

#### 해결 방법

신규 테스트에서 `result.error.toString()`이 raw encoder 문구를 포함하지 않고, `workflow[0]` 또는 `Validate Pipeline` 및 unsupported task 메시지를 포함하는지 단언한다.
구현 방식에 따라 `Pipeline.pipelineInitialize()`에서 key 타입을 직접 검사해도 된다.

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

- [ ] `lib/oto/pipeline/pipeline.dart` 또는 `lib/oto/application.dart`: non-string workflow key가 명시 validation message로 실패하게 한다.
- [ ] `test/oto_application_test.dart` 또는 `test/oto_core_test.dart`: `Converting object to an encodable object failed`가 노출되지 않는다는 단언을 포함한다.

#### 중간 검증

```bash
dart test test/oto_application_test.dart test/oto_core_test.dart
```

기대 결과: 신규 메시지 테스트와 기존 테스트가 모두 통과한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/oto/application.dart` | REVIEW_REVIEW_REFACTOR-1 |
| `lib/oto/pipeline/pipeline.dart` | REVIEW_REVIEW_REFACTOR-2 필요 시 |
| `test/oto_application_test.dart` | REVIEW_REVIEW_REFACTOR-1~2 |
| `test/oto_core_test.dart` | 검증 대상 |

## 최종 검증

```bash
dart analyze
dart test test/oto_application_test.dart test/oto_core_test.dart
dart run bin/main.dart exe -f assets/yaml/sample/01_basic.yaml
dart run bin/main.dart exe -f assets/yaml/sample/02_pipeline_if.yaml
```

기대 결과: analyze와 테스트가 통과하고, 두 샘플 실행이 `Build Successfully Complete`를 출력한다.

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