<!-- task=02_pipeline_yaml_validation plan=1 tag=REVIEW_REVIEW_VALIDATE -->

# Pipeline YAML Validation Follow-up

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

**구현의 마지막 단계는 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이다. 이 파일이 채워지기 전에는 작업이 완료된 것이 아니다.** 구현 체크리스트를 기준으로 작업하고, 중간/최종 검증을 실행한 뒤 실제 구현 내용과 stdout/stderr를 review stub에 기록한다. review 파일의 archive/complete.log 지시는 코드리뷰 스킬 전용이며 구현 에이전트가 실행하거나 `코드리뷰 전용 체크리스트`를 수정하면 안 된다.

## 배경

`REVIEW_VALIDATE` 1차 리뷰에서 일부 malformed YAML이 아직 generated `fromJson` cast 또는 직접 `Data*.fromJson` 경로에 남아 있음이 확인됐다. 목표는 남은 pipeline YAML 구조 오류도 Dart 내부 TypeError 대신 위치와 필드명을 포함한 validation message로 반환하게 만드는 것이다.

## 분석 결과

### 읽어야 할 파일

- `lib/oto/application.dart`
- `lib/oto/data/pipeline_data.dart`
- `lib/oto/data/pipeline_data.g.dart`
- `lib/oto/pipeline/pipeline_contain.dart`
- `lib/oto/pipeline/pipeline_exe_handle.dart`
- `lib/oto/pipeline/pipeline_if.dart`
- `lib/oto/pipeline/pipeline_while.dart`
- `lib/oto/pipeline/pipeline_foreach.dart`
- `lib/oto/pipeline/pipeline_switch.dart`
- `test/oto_application_test.dart`
- `test/oto_core_test.dart`

### 범위 결정 근거

이번 후속 작업은 1차 리뷰 Required 항목만 다룬다. 커맨드 파라미터 모델, 샘플 YAML, command catalog 변경은 이 task 범위가 아니다.

### 빌드 등급

build=`cloud-G07`, review=`cloud-G07`: YAML schema validation은 여러 pipeline node와 generated data model 경계에 걸친 cross-node 계약이다.

## 구현 체크리스트

- [ ] [REVIEW_REVIEW_VALIDATE-1] `pipeline.id` missing/non-string이 generated cast 전에 validation message로 반환되도록 검증한다.
- [ ] [REVIEW_REVIEW_VALIDATE-2] `contain` pipeline node의 raw map, `target`, `list`, branch list 타입을 명시 검증한다.
- [ ] [REVIEW_REVIEW_VALIDATE-3] branch workflow/list 필드(`on-success`, `on-fail`, `on-true`, `on-false`, `on-do`, `cases[]`, `tasks`)가 필드명 포함 validation message를 반환하도록 보강하고 회귀 테스트를 추가한다.
- [ ] 중간 검증과 최종 검증 명령을 실행하고 결과를 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_VALIDATE-1] DataPipeline id validation

문제: [application.dart](/config/workspace/oto/lib/oto/application.dart:218)는 `pipeline.workflow`만 사전 검증하고, [pipeline_data.g.dart](/config/workspace/oto/lib/oto/data/pipeline_data.g.dart:10)의 `json['id'] as String`이 먼저 실패할 수 있다.

해결 방법: `DataBuild.fromJson(buildMap)` 호출 전에 `_validateBuildMap`에서 `pipeline['id']`를 non-empty string으로 검증한다. 모델을 nullable로 바꾸는 방식은 필요할 때만 선택하고, 선택 시 `build_runner` 생성 파일을 동기화한다.

Before:

```dart
final workflow = pipeline['workflow'];
```

After:

```dart
final pipelineId = pipeline['id'];
if (pipelineId is! String || pipelineId.trim().isEmpty) {
  return _ValidateResult(
      false, '"pipeline.id" must be a non-empty string.');
}
final workflow = pipeline['workflow'];
```

수정 파일 및 체크리스트:

- [ ] `lib/oto/application.dart`: `pipeline.id` 필수 string 검증 추가.
- [ ] `test/oto_application_test.dart`: missing id, non-string id 중 최소 1개 이상 회귀 테스트 추가.

테스트 작성: 작성. 에러 메시지에 `pipeline.id`가 포함되고 `is not a subtype`이 포함되지 않음을 확인한다.

중간 검증:

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

### [REVIEW_REVIEW_VALIDATE-2] contain node guard

문제: [pipeline_contain.dart](/config/workspace/oto/lib/oto/pipeline/pipeline_contain.dart:16)가 `DataContain.fromJson(set.values.first)`를 직접 호출한다. `contain: 123`, `target: 123`, branch list 타입 오류가 validation result로 안정적으로 변환되지 않는다.

해결 방법: 다른 pipeline node와 같은 패턴으로 raw value가 `Map`인지 먼저 확인하고, `target`은 non-empty string, `list`는 `List` 또는 `String`, `on-true`/`on-false`는 존재 시 `List`인지 fromJson 전에 검증한다. `TypeError` catch는 최후 방어로만 둔다.

Before:

```dart
var data = DataContain.fromJson(set.values.first);
```

After:

```dart
final raw = set.values.first;
if (raw is! Map) {
  return invalid('contain: value must be a map.');
}
final rawMap = Map<String, dynamic>.from(raw);
// validate target/list/on-true/on-false before DataContain.fromJson(rawMap)
```

수정 파일 및 체크리스트:

- [ ] `lib/oto/pipeline/pipeline_contain.dart`: raw map 및 필드 타입 guard 추가.
- [ ] `lib/oto/data/pipeline_data.dart`: `DataContain.validate()`가 missing/empty `target`을 명시 처리하도록 보강 필요 여부 확인.
- [ ] `lib/oto/data/pipeline_data.g.dart`: 모델 변경 시 build_runner로 갱신.
- [ ] `test/oto_core_test.dart`: malformed `contain` 테스트 추가.

테스트 작성: 작성. `contain` node name과 오류 field name을 모두 확인한다.

중간 검증:

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

### [REVIEW_REVIEW_VALIDATE-3] branch list field validation

문제: `exe-handle`, `if`, `while`, `foreach`, `switch`의 하위 workflow/list 필드 중 일부는 `fromJson` 실패를 `invalid field type`으로만 포괄 처리한다. 1차 계획의 "위치와 필드명을 포함한 validation message" 체크포인트를 만족하려면 필드명 단위 검증이 필요하다.

해결 방법: 각 initialize에서 fromJson 전에 다음을 검증한다.

- `exe-handle.on-success`, `exe-handle.on-fail`: required non-empty `List`
- `if.on-true`, `if.on-false`: 존재 시 `List`, 둘 중 하나 이상 존재
- `while.on-do`, `foreach.on-do`: required `List`
- `switch.cases`: required `List`
- `switch.cases[index]`: `Map`
- `switch.cases[index].tasks`: required `List`

수정 파일 및 체크리스트:

- [ ] `lib/oto/pipeline/pipeline_exe_handle.dart`: branch list guard를 필드명 포함 메시지로 보강.
- [ ] `lib/oto/pipeline/pipeline_if.dart`: `on-true`/`on-false` 타입 guard 추가.
- [ ] `lib/oto/pipeline/pipeline_while.dart`: `on-do` 타입 guard 추가.
- [ ] `lib/oto/pipeline/pipeline_foreach.dart`: `on-do` 타입 guard 추가.
- [ ] `lib/oto/pipeline/pipeline_switch.dart`: `cases[]`/`tasks` 타입 guard 추가.
- [ ] `test/oto_core_test.dart` 또는 `test/oto_application_test.dart`: 최소 각 failure category별 field-name assertion 추가.

테스트 작성: 작성. 기존 테스트의 `isNot(contains('is not a subtype'))`만으로 끝내지 말고 node name과 field name을 모두 확인한다.

중간 검증:

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

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/oto/application.dart` | REVIEW_REVIEW_VALIDATE-1 |
| `lib/oto/data/pipeline_data.dart` | REVIEW_REVIEW_VALIDATE-2 |
| `lib/oto/data/pipeline_data.g.dart` | REVIEW_REVIEW_VALIDATE-2 |
| `lib/oto/pipeline/pipeline_contain.dart` | REVIEW_REVIEW_VALIDATE-2 |
| `lib/oto/pipeline/pipeline_exe_handle.dart` | REVIEW_REVIEW_VALIDATE-3 |
| `lib/oto/pipeline/pipeline_if.dart` | REVIEW_REVIEW_VALIDATE-3 |
| `lib/oto/pipeline/pipeline_while.dart` | REVIEW_REVIEW_VALIDATE-3 |
| `lib/oto/pipeline/pipeline_foreach.dart` | REVIEW_REVIEW_VALIDATE-3 |
| `lib/oto/pipeline/pipeline_switch.dart` | REVIEW_REVIEW_VALIDATE-3 |
| `test/oto_application_test.dart` | REVIEW_REVIEW_VALIDATE-1, REVIEW_REVIEW_VALIDATE-3 |
| `test/oto_core_test.dart` | REVIEW_REVIEW_VALIDATE-2, REVIEW_REVIEW_VALIDATE-3 |

## 최종 검증

```bash
dart run build_runner build --delete-conflicting-outputs
dart analyze
dart test
```

기대 결과: generated files up to date, `No issues found!`, `All tests passed!`

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