<!-- task=m-proto-socket-infrastructure-communication-rail/01_core_endpoint plan=1 tag=REVIEW_CORE-ENDPOINT -->

# Plan - REVIEW_CORE-ENDPOINT

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

구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이다. 검증 명령을 실행하고 실제 stdout/stderr를 남긴 뒤 active 파일을 그대로 둔 채 리뷰 준비 상태를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.

구현 중 사용자만 결정할 수 있는 사항, 외부 환경 준비, 또는 계획 범위 충돌로 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 사항, 근거, 실행한 명령과 출력, 재개 조건을 채우고 중단한다. 구현 에이전트는 `USER_REVIEW.md`, archive log, `complete.log`를 직접 만들지 않는다.

## 배경

이 후속 계획은 `code_review_cloud_G08_0.log`의 `FAIL` Required 항목만 닫는다. 핵심은 repo-external proto-socket `replace` 제거, semantic envelope error contract 보강, authenticated route exposure test 추가, Go formatting 회복이다. 기존 proto-socket endpoint 설계와 task channel 후속 범위는 확장하지 않는다.

## 사용자 리뷰 요청 흐름

구현 중 차단 사항은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 내용을 사용하며, code-review가 검증한 뒤 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## 분석 결과

### 읽은 파일

- `agent-task/m-proto-socket-infrastructure-communication-rail/01_core_endpoint/code_review_cloud_G08_0.log`
- `agent-task/m-proto-socket-infrastructure-communication-rail/01_core_endpoint/plan_cloud_G08_0.log`
- `services/core/go.mod`
- `services/core/go.sum`
- `../proto-socket/examples/go-module-consumer/go.mod`
- `../proto-socket/go/go.mod`
- `services/core/internal/protosocket/envelope.go`
- `services/core/internal/protosocket/dispatcher.go`
- `services/core/internal/protosocket/server.go`
- `services/core/internal/protosocket/envelope_test.go`
- `services/core/internal/protosocket/server_test.go`
- `services/core/internal/http/router.go`
- `services/core/internal/http/middleware_test.go`
- `services/core/internal/config/config.go`
- `services/core/internal/config/config_test.go`
- `packages/contracts/notes/flutter-core-api-candidates.md`

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

- proto-socket module resolution: 현재 테스트 출력은 sibling `../../../proto-socket/go` replace가 있는 상태에서만 의미가 있다. repo 단독 checkout에서 fetch 가능한 module requirement 검증이 필요하다.
- envelope error contract: contract note의 `retryable` 필드가 `EnvelopeError` round trip과 unsupported action error test에 없다.
- router auth exposure: `/proto-socket` 무인증 `401`은 검증하지만, 유효한 Basic Auth 요청이 mounted handler까지 도달하는지는 검증하지 않는다.
- formatting: `gofmt -l`이 `services/core/internal/config/config.go`를 보고한다.

### 심볼 참조

- 제거/rename 심볼 없음.
- `EnvelopeError` 필드 추가는 `services/core/internal/protosocket/envelope.go`, `dispatcher.go`, `server.go`, `envelope_test.go`, `server_test.go`의 struct literal과 변환 테스트만 확인하면 된다.

### 분할 판단

Split decision policy를 평가했다. 기존 split subtask `01_core_endpoint`의 리뷰 follow-up이며, Required 항목들이 같은 endpoint foundation의 계약/검증 회복에 묶여 있어 새 sibling subtask로 나누지 않는다.

### 범위 결정 근거

- `02+01_task_channels`는 시작하지 않는다.
- `apps/client/**`는 후속 client rail 범위라 수정하지 않는다.
- `../proto-socket/**` sibling repo는 참조만 하고 수정하지 않는다.
- proto-socket action handler 추가, broadcast semantics 변경, origin policy 변경은 이번 follow-up 범위가 아니다.

### 빌드 등급

`cloud-G08`. protocol dependency, API contract, auth route test, verification trust 회복이 포함된 기존 cloud-G08 리뷰 follow-up이다.

## 구현 체크리스트

- [ ] `services/core`의 proto-socket dependency에서 repo-external `replace`와 zero pseudo-version을 제거하고 publish된 module version으로 재정리한다.
- [ ] `EnvelopeError`의 `retryable` contract를 구현하고 envelope/error 테스트를 갱신한다.
- [ ] `/proto-socket` authenticated route exposure test를 추가한다.
- [ ] `services/core/internal/config/config.go`를 `gofmt`로 정리한다.
- [ ] 허용된 비로컬 테스트 환경에서 module tidy, focused Go tests, full Go tests, formatting/diff 검증을 `-count=1` 기준으로 실행하고 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

이 subtask는 기존 `01_core_endpoint`의 follow-up이다. 같은 디렉터리 안에서 현재 Required 항목을 닫고, PASS 전까지 `02+01_task_channels`는 시작하지 않는다.

### [REVIEW_CORE-ENDPOINT-1] Remove repo-external proto-socket replace

#### 문제

`services/core/go.mod:6`은 proto-socket을 zero pseudo-version으로 require하고, `services/core/go.mod:35`는 `../../../proto-socket/go` sibling checkout을 `replace`로 참조한다. 이 상태는 NomadCode 저장소 단독 checkout, CI, 배포 환경에서 재현되지 않는다.

#### 해결 방법

`../proto-socket/examples/go-module-consumer/go.mod:5`가 사용하는 publish된 pseudo-version을 기준으로 require를 고정하고 repo-external `replace`를 제거한다. 허용된 비로컬 환경에서 private module fetch가 실패하면 local replace를 다시 넣지 말고 active review stub의 `사용자 리뷰 요청`에 명령/출력/재개 조건을 기록한다.

Before:

```go
require (
	git.toki-labs.com/toki/proto-socket/go v0.0.0-00010101000000-000000000000
)

replace git.toki-labs.com/toki/proto-socket/go => ../../../proto-socket/go
```

After:

```go
require (
	git.toki-labs.com/toki/proto-socket/go v0.0.0-20260501220005-284b66a22300
)
```

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

- [ ] `services/core/go.mod`: zero pseudo-version과 repo-external `replace`를 제거한다.
- [ ] `services/core/go.sum`: `go mod tidy` 결과에 맞춰 갱신한다.

#### 테스트 작성

- 작성하지 않음. module resolution은 manifest와 비로컬 `go mod tidy`/`go test` 검증으로 확인한다.

#### 중간 검증

```bash
cd services/core && go mod tidy
cd services/core && go list -m git.toki-labs.com/toki/proto-socket/go
```

예상 결과: sibling `replace` 없이 proto-socket module version이 해석된다.

### [REVIEW_CORE-ENDPOINT-2] Add retryable to envelope error contract

#### 문제

`packages/contracts/notes/flutter-core-api-candidates.md:62`는 error 후보 필드에 `retryable`을 포함하지만 `services/core/internal/protosocket/envelope.go:25`의 `EnvelopeError`는 해당 필드를 표현하지 못한다. 따라서 Core가 만든 error envelope와 contract note가 어긋난다.

#### 해결 방법

`EnvelopeError`에 `Retryable bool`을 추가하고 `ToStruct`/`EnvelopeFromStruct`가 `retryable`을 round trip하도록 한다. Unsupported action, invalid envelope, serialization error는 기본적으로 retry 불가이므로 `Retryable: false`를 명시하거나 zero value가 구조 변환에 포함되게 한다.

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

- [ ] `services/core/internal/protosocket/envelope.go`: `Retryable bool` 필드와 struct 변환을 추가한다.
- [ ] `services/core/internal/protosocket/envelope_test.go`: round trip에서 `retryable`을 검증한다.
- [ ] `services/core/internal/protosocket/server_test.go`: unsupported action error response의 `Retryable == false`를 검증한다.

#### 테스트 작성

- 작성: `TestEnvelopeStructRoundTrip`에 `Retryable` assertion을 추가한다.
- 작성: `TestServerRespondsWithErrorEnvelopeForUnsupportedAction`에 `Retryable == false` assertion을 추가한다.

#### 중간 검증

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

예상 결과: envelope round trip과 unsupported action response tests PASS.

### [REVIEW_CORE-ENDPOINT-3] Verify authenticated proto-socket route exposure

#### 문제

`services/core/internal/http/middleware_test.go:46`의 router test는 `/proto-socket` 무인증 `401`만 확인한다. 유효한 Basic Auth 요청이 mounted proto-socket handler까지 도달하는지 검증하지 않아 endpoint 노출 요구가 테스트로 닫히지 않는다.

#### 해결 방법

같은 테스트 또는 새 테스트에서 `req.SetBasicAuth("nomadcode", "secret")`를 적용한 `/proto-socket` 요청을 보내고 mock handler가 `200`을 반환하는지 확인한다.

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

- [ ] `services/core/internal/http/middleware_test.go`: authenticated `/proto-socket` exposure assertion을 추가한다.

#### 테스트 작성

- 작성: `/proto-socket` with valid Basic Auth -> `http.StatusOK`.

#### 중간 검증

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

예상 결과: `/healthz` public, `/api/tasks` protected, `/proto-socket` unauthenticated protected, `/proto-socket` authenticated exposed assertions PASS.

### [REVIEW_CORE-ENDPOINT-4] Restore Go formatting

#### 문제

`gofmt -l services/core/internal/config/config.go ...`가 `services/core/internal/config/config.go`를 출력했다. 현재 `Config`와 `Load()`의 proto-socket 필드 alignment가 gofmt 상태가 아니다.

#### 해결 방법

`services/core/internal/config/config.go`를 gofmt로 정리한다. 다른 파일에는 formatting-only churn을 만들지 않는다.

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

- [ ] `services/core/internal/config/config.go`: gofmt 적용.

#### 테스트 작성

- 작성하지 않음. formatting correction이다.

#### 중간 검증

```bash
gofmt -l services/core/internal/config/config.go
```

예상 결과: 출력 없음.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/go.mod` | REVIEW_CORE-ENDPOINT-1 |
| `services/core/go.sum` | REVIEW_CORE-ENDPOINT-1 |
| `services/core/internal/protosocket/envelope.go` | REVIEW_CORE-ENDPOINT-2 |
| `services/core/internal/protosocket/envelope_test.go` | REVIEW_CORE-ENDPOINT-2 |
| `services/core/internal/protosocket/server_test.go` | REVIEW_CORE-ENDPOINT-2 |
| `services/core/internal/http/middleware_test.go` | REVIEW_CORE-ENDPOINT-3 |
| `services/core/internal/config/config.go` | REVIEW_CORE-ENDPOINT-4 |

## 최종 검증

로컬 테스트 실행은 `agent-test/local/rules.md`에 따라 금지다. 구현 에이전트는 허용된 비로컬 테스트 환경에서 아래 명령을 그대로 실행한다. Go test cache output은 허용하지 않으며 `-count=1`을 유지한다.

```bash
cd services/core && go mod tidy
```

예상 결과: repo-external `replace` 없이 dependency graph 정리 성공.

```bash
cd services/core && go list -m git.toki-labs.com/toki/proto-socket/go
```

예상 결과: publish된 proto-socket module version 출력.

```bash
cd services/core && go test -count=1 ./internal/config ./internal/protosocket ./internal/http
```

예상 결과: config formatting 대상 코드는 compile되고 envelope/router focused tests PASS.

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

예상 결과: Core 전체 package PASS.

```bash
gofmt -l services/core/internal/config/config.go services/core/internal/protosocket/envelope.go services/core/internal/protosocket/envelope_test.go services/core/internal/protosocket/server_test.go services/core/internal/http/middleware_test.go
```

예상 결과: 출력 없음.

```bash
git diff --check
```

예상 결과: whitespace error 없음.

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