<!-- task=workflow_core/03+01_notification_events plan=0 tag=WORKFLOW -->

# Workflow Core Notification Events

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

`01_lifecycle_foundation`이 PASS되어 `complete.log`가 생긴 뒤 시작한다. `CODE_REVIEW-cloud-G06.md`의 구현 에이전트 소유 섹션 작성은 필수이며, 구현 후 검증 출력과 계획 대비 변경 사항을 채운 뒤 active 파일을 그대로 둔다. 최종 판정과 archive 이동은 code-review skill 전용이다.

## 배경

Workflow Core Milestone의 notification 최소 범위는 외부 협업 도구 발송이 아니라 core 내부 상태 변화 event 모델과 발행 지점이다. 현재 notification service는 completed 전용 메서드만 있고 scheduler도 completed 때만 호출한다. 이 작업은 lifecycle 상태 변화가 notification event로 관찰되도록 일반화한다.

## 분석 결과

### 읽은 파일

- `agent-ops/roadmap/current.md`
- `agent-ops/roadmap/milestones/workflow-core.md`
- `services/core/internal/notification/model.go`
- `services/core/internal/notification/service.go`
- `services/core/internal/adapters/mattermost/client.go`
- `services/core/internal/scheduler/jobs.go`
- `services/core/internal/scheduler/jobs_test.go`
- `services/core/internal/workflow/model.go`
- `services/core/go.mod`
- `services/core/bin/test`
- `bin/test`

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

- notification service에는 테스트 파일이 없다.
- scheduler worker는 completed notification 호출 여부를 검증하지 않는다.
- failed/running/canceled 상태 변화 event는 모델과 발행 지점이 없다.

### 심볼 참조

- renamed/removed symbols: none.
- 기존 call site: `services/core/internal/scheduler/jobs.go:61-67`에서 `NotifyTaskCompleted`를 호출한다.

### 분할 판단

split decision policy를 먼저 평가했다. 이 계획은 `workflow_core/03+01_notification_events`이며 `workflow_core/01_lifecycle_foundation`에 의존한다. retry/timeout metadata는 `02+01_retry_timeout`에서 다루며, 이 계획은 event 모델과 scheduler 발행 지점만 소유한다.

### 범위 결정 근거

- Mattermost 실제 메시지 발송 확장은 제외한다. 기존 completed notification 호환만 유지한다.
- provider comment/status/label projection worker는 제외한다. provider projection은 External Integration 또는 별도 provider projection plan에서 다룬다.
- notification persistence/outbox table은 제외한다. 현재 Milestone의 최소 범위는 in-process event model과 발행 지점이다.

### 빌드 등급

build=`cloud-G06`, review=`cloud-G06`: 범위는 작지만 scheduler side effect와 기존 Mattermost 호환을 건드린다.

## 구현 체크리스트

- [ ] [WORKFLOW-1] notification event 모델과 service API를 일반화한다.
- [ ] [WORKFLOW-2] scheduler worker가 running/completed/failed event를 발행하게 한다.
- [ ] 중간 검증과 최종 검증을 모두 실행하고 실제 출력을 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [WORKFLOW-1] notification event 모델과 service API를 일반화한다

#### 문제

`services/core/internal/notification/model.go:3-8`의 `TaskNotification`은 completed message에 가까운 형태다. `services/core/internal/notification/service.go:22`의 `NotifyTaskCompleted`는 completed 전용이라 failed/running/canceled event를 표현할 수 없다.

#### 해결 방법

notification model을 task lifecycle event로 확장하되 기존 `NotifyTaskCompleted`는 호환 wrapper로 유지한다.

Before (`services/core/internal/notification/service.go:22`):

```go
func (s *Service) NotifyTaskCompleted(ctx context.Context, input TaskNotification) error {
```

After:

```go
type TaskEventType string

const (
    TaskEventRunning   TaskEventType = "task.running"
    TaskEventCompleted TaskEventType = "task.completed"
    TaskEventFailed    TaskEventType = "task.failed"
    TaskEventCanceled   TaskEventType = "task.canceled"
)

type TaskEvent struct {
    Type    TaskEventType
    TaskID  string
    Title   string
    Status  string
    Message string
}

func (s *Service) NotifyTaskEvent(ctx context.Context, input TaskEvent) error
```

`NotifyTaskCompleted`는 `NotifyTaskEvent(ctx, TaskEvent{Type: TaskEventCompleted, ...})`로 위임한다. Mattermost 호출은 completed event에서만 기존 동작을 유지하고, 다른 event는 logger에만 남긴다.

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

- [ ] `services/core/internal/notification/model.go`: `TaskEventType`, `TaskEvent`를 추가한다.
- [ ] `services/core/internal/notification/service.go`: `NotifyTaskEvent` 추가, `NotifyTaskCompleted` wrapper 유지.
- [ ] `services/core/internal/notification/service_test.go`: completed event 호환과 failed event no-op/log path를 검증한다.

#### 테스트 작성

작성한다.

- `TestNotifyTaskCompletedUsesTaskEventCompleted`: completed wrapper가 general event path를 사용하는지 검증한다.
- `TestNotifyTaskEventAllowsFailedWithoutMattermost`: Mattermost nil 상태에서 failed event가 error 없이 처리되는지 검증한다.

#### 중간 검증

```bash
cd services/core && go test -count=1 ./internal/notification
```

기대 결과: notification package tests pass. Go test cache output은 허용하지 않는다.

### [WORKFLOW-2] scheduler worker가 running/completed/failed event를 발행하게 한다

#### 문제

`services/core/internal/scheduler/jobs.go:61-67`은 completed 후에만 notification을 보낸다. `jobs.go:44` running 전환과 `jobs.go:50-52`, `55-58` failure path는 notification event로 관찰되지 않는다.

#### 해결 방법

`01_lifecycle_foundation`의 lifecycle API 호출 직후 event를 발행한다. Notification 실패는 현재 completed notification처럼 task 상태를 되돌리지 않고 warn log만 남긴다.

Before (`services/core/internal/scheduler/jobs.go:61-67`):

```go
err = w.Notifications.NotifyTaskCompleted(ctx, notification.TaskNotification{
    TaskID:  task.ID,
    Title:   task.Title,
    Status:  task.Status,
    Message: message,
})
```

After:

```go
w.notifyTaskEvent(ctx, notification.TaskEvent{
    Type:    notification.TaskEventCompleted,
    TaskID:  task.ID,
    Title:   task.Title,
    Status:  task.Status,
    Message: message,
})
```

Add helper:

```go
func (w *TaskWorker) notifyTaskEvent(ctx context.Context, event notification.TaskEvent)
```

Emit:

- after successful `StartTask`: `TaskEventRunning`
- after successful `CompleteTask`: `TaskEventCompleted`
- after successful `FailTask`: `TaskEventFailed`

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

- [ ] `services/core/internal/scheduler/jobs.go`: add helper and event calls.
- [ ] `services/core/internal/scheduler/jobs_test.go`: fake notification sink or service test seam을 추가해 completed/failed events를 검증한다.

#### 테스트 작성

작성한다.

- `TestWorkEmitsRunningAndCompletedEvents`: success path emits event types in order.
- `TestWorkEmitsFailedEvent`: run error path emits failed event and does not swallow worker error.

#### 중간 검증

```bash
cd services/core && go test -count=1 ./internal/scheduler
```

기대 결과: scheduler package tests pass. Go test cache output은 허용하지 않는다.

## 의존 관계 및 구현 순서

이 subtask directory는 `03+01_notification_events`이므로 `agent-task/workflow_core/01_lifecycle_foundation/complete.log`가 있어야 시작한다.

1. `WORKFLOW-1` notification service API를 일반화한다.
2. `WORKFLOW-2` scheduler event 발행 지점을 붙인다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/notification/model.go` | WORKFLOW-1 |
| `services/core/internal/notification/service.go` | WORKFLOW-1 |
| `services/core/internal/notification/service_test.go` | WORKFLOW-1 |
| `services/core/internal/scheduler/jobs.go` | WORKFLOW-2 |
| `services/core/internal/scheduler/jobs_test.go` | WORKFLOW-2 |

## 최종 검증

```bash
cd services/core && go test -count=1 ./...
```

기대 결과: all core Go tests pass. Go test cache output은 허용하지 않는다.

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