<!-- task=m-openai-workspace-agent-execution-contract/01_route_catalog_workspace_bound plan=0 tag=API -->

# Plan - API

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

`CODE_REVIEW-local-G05.md`의 구현 에이전트 소유 섹션을 채우는 것까지가 구현이다. 코드를 수정하고 검증을 실행한 뒤 실제 변경 내용, 명령 출력, 계획 대비 변경 사항을 기록하고 active 파일을 그대로 둔 채 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경, scope 충돌로 막히면 사용자에게 직접 묻지 말고 review stub의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 채운다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 전용이다.

## 배경

`metadata-schema` 선행 작업으로 `metadata.workspace`는 Edge service의 `RunRequest.Workspace`까지 전달된다. 남은 `route-catalog` Task는 외부 `model`이 내부 `adapter + target`으로 해석될 때 어떤 route가 workspace-bound agent target인지 config와 smoke fixture에 남겨야 한다. 이 표식이 있어야 후속 `workspace-required` Task가 inference route는 유지하면서 agent route에만 workspace 필수 조건을 적용할 수 있다.

## 사용자 리뷰 요청 흐름

구현 중 사용자 결정이 필요하면 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 active `CODE_REVIEW-local-G05.md`의 `사용자 리뷰 요청` 섹션을 채운다. 구현 에이전트는 채팅으로 직접 질문하거나 `request_user_input`을 호출하지 않는다. code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/openai-workspace-agent-execution-contract.md`
- Task ids:
  - `route-catalog`: 외부 `model` route가 내부 `adapter + target`으로 해석되는 기준을 config와 smoke fixture에 남긴다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/openai-workspace-agent-execution-contract.md`
- `agent-contract/index.md`
- `agent-contract/provided/openai-compatible-api.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `packages/go/config/config.go`
- `packages/go/config/config_test.go`
- `configs/edge.yaml`
- `configs/edge-compose.yaml.tmpl`
- `scripts/e2e-openai-ollama.sh`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/responses_handler.go`
- `apps/edge/internal/openai/routes.go`
- `apps/edge/internal/openai/server_test.go`
- `apps/edge/internal/edgecmd/smoke_openai.go`

### 테스트 환경 규칙

`test_env=local`을 선택했다. `agent-test/local/rules.md`를 읽었고, edge 변경 기준으로 `agent-test/local/edge-smoke.md`, config 변경 기준으로 `agent-test/local/platform-common-smoke.md`를 읽었다. 적용 명령은 `go test ./packages/go/config ./apps/edge/internal/openai`, 최종 smoke는 OpenAI 입력 표면 기준 `./scripts/e2e-openai-ollama.sh`다. 규칙 파일에 구조적 blank나 `<확인 필요>` 값은 없었다.

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

route catalog dispatch는 `apps/edge/internal/openai/server_test.go`에 있지만 workspace-bound route 표식은 현재 없다. config load는 `packages/go/config/config_test.go`에 route catalog test가 있지만 `workspace_required` field coverage가 없다. `scripts/e2e-openai-ollama.sh`는 현재 top-level `target/models` fallback을 주로 쓰므로 route catalog smoke fixture coverage가 약하다.

### 심볼 참조

renamed/removed symbols: none.

### 분할 판단

split decision policy를 평가했다. 이 task group은 `agent-task/m-openai-workspace-agent-execution-contract/`이고 sibling은 `01_route_catalog_workspace_bound`와 `02+01_workspace_required_enforcement`다. `01_route_catalog_workspace_bound`는 선행 config/fixture 계약 작업이며 dependency가 없다. `02+01_workspace_required_enforcement`는 route catalog 표식을 소비하므로 predecessor `01`의 `complete.log`가 필요하다.

### 범위 결정 근거

이 계획은 route catalog에 workspace-bound 표식을 추가하고 config/smoke fixture에 남기는 데 한정한다. `metadata.workspace` 파싱은 이미 작은 선행 변경으로 처리되었으므로 확장하지 않는다. workspace 누락/상대 경로 거부는 후속 `02+01_workspace_required_enforcement`가 맡는다. Node cwd 적용, session workspace isolation, NomadCode 호출 shape는 다른 Epic/Task 범위라 제외한다.

### 빌드 등급

build lane은 `local-G05`다. config schema, YAML fixture, focused Go tests로 검증 가능하지만 public config 계약에 닿아 중간 등급으로 둔다.

## 구현 체크리스트

- [ ] `OpenAIRouteEntry`와 route dispatch에 workspace-bound agent route 표식을 추가하고 config load test를 갱신한다.
- [ ] `configs/edge.yaml`와 OpenAI smoke fixture가 route catalog 기준을 보여주도록 갱신한다.
- [ ] `go test ./packages/go/config ./apps/edge/internal/openai`와 `./scripts/e2e-openai-ollama.sh`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Route Catalog Workspace-Bound Flag

문제: `packages/go/config/config.go:90`의 `OpenAIRouteEntry`는 external model을 adapter/target으로 매핑하지만 workspace-bound agent target 여부를 표현하지 못한다. `apps/edge/internal/openai/chat_handler.go:156`의 `routeDispatch`도 이 정보를 후속 검증 로직에 전달할 필드가 없다.

해결 방법: `workspace_required` YAML/mapstructure field를 `OpenAIRouteEntry`에 추가하고, route catalog match path에서 `routeDispatch.WorkspaceRequired`로 복사한다. fallback route는 `false`를 유지한다.

Before:

```go
// packages/go/config/config.go:90
type OpenAIRouteEntry struct {
	Model      string `mapstructure:"model"       yaml:"model"`
	NodeRef    string `mapstructure:"node"        yaml:"node,omitempty"`
	Adapter    string `mapstructure:"adapter"     yaml:"adapter,omitempty"`
	Target     string `mapstructure:"target"      yaml:"target"`
	SessionID  string `mapstructure:"session_id"  yaml:"session_id,omitempty"`
	TimeoutSec int    `mapstructure:"timeout_sec" yaml:"timeout_sec,omitempty"`
}
```

After:

```go
type OpenAIRouteEntry struct {
	Model             string `mapstructure:"model"              yaml:"model"`
	NodeRef           string `mapstructure:"node"               yaml:"node,omitempty"`
	Adapter           string `mapstructure:"adapter"            yaml:"adapter,omitempty"`
	Target            string `mapstructure:"target"             yaml:"target"`
	SessionID         string `mapstructure:"session_id"         yaml:"session_id,omitempty"`
	TimeoutSec        int    `mapstructure:"timeout_sec"        yaml:"timeout_sec,omitempty"`
	WorkspaceRequired bool   `mapstructure:"workspace_required" yaml:"workspace_required,omitempty"`
}
```

수정 파일 및 체크리스트:

- [ ] `packages/go/config/config.go`: `OpenAIRouteEntry.WorkspaceRequired` 추가.
- [ ] `packages/go/config/config_test.go`: `TestLoadEdge_OpenAIRouteCatalog`에 `workspace_required: true` fixture와 assertion 추가.
- [ ] `apps/edge/internal/openai/chat_handler.go`: `routeDispatch.WorkspaceRequired` 추가, catalog match에서 복사, fallback false 유지.
- [ ] `apps/edge/internal/openai/server_test.go`: route dispatch가 `workspace_required`를 보존하는 package-level test 추가.

테스트 작성: 작성한다. config load test는 YAML parsing을, openai package test는 dispatch DTO 보존을 검증한다.

중간 검증:

```bash
go test ./packages/go/config ./apps/edge/internal/openai
```

기대 결과: 두 패키지 모두 PASS.

### [API-2] Config And Smoke Fixture Route Catalog Evidence

문제: `configs/edge.yaml:56`의 `model_routes` 예시는 inference provider route만 보여주고 `model: "codex"` 같은 workspace-bound agent route 기준을 남기지 않는다. `scripts/e2e-openai-ollama.sh:123`의 smoke config는 `target/models` fallback 중심이라 route catalog smoke evidence가 약하다.

해결 방법: `configs/edge.yaml` 주석 예시에 CLI agent route를 추가하고 `workspace_required: true`를 명시한다. `scripts/e2e-openai-ollama.sh`의 임시 config에 `model_routes`를 추가해 `/v1/models`, Chat Completions, Responses가 catalog route를 통과하게 하되 Ollama smoke route는 workspace-required로 만들지 않는다.

Before:

```yaml
# configs/edge.yaml:59
# model_routes:
#   - model: "llama3"
#     adapter: "ollama"
#     target: "llama3:8b"
```

After:

```yaml
# model_routes:
#   - model: "codex"
#     adapter: "cli"
#     target: "codex"
#     workspace_required: true
#   - model: "llama3"
#     adapter: "ollama"
#     target: "llama3:8b"
```

수정 파일 및 체크리스트:

- [ ] `configs/edge.yaml`: codex workspace-bound route 예시 추가.
- [ ] `scripts/e2e-openai-ollama.sh`: temp `openai.model_routes` fixture 추가, model assertions/request model이 catalog route를 검증하도록 조정.
- [ ] `apps/edge/README.md`: route catalog 설명이 `workspace_required`를 언급하도록 필요한 최소 문장만 갱신.

테스트 작성: 별도 단위 테스트보다 기존 smoke script를 route catalog fixture로 바꾸는 것이 검증이다.

중간 검증:

```bash
./scripts/e2e-openai-ollama.sh
```

기대 결과: `OpenAI-compatible Ollama serving test PASSED.` 출력.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/config/config.go` | API-1 |
| `packages/go/config/config_test.go` | API-1 |
| `apps/edge/internal/openai/chat_handler.go` | API-1 |
| `apps/edge/internal/openai/server_test.go` | API-1 |
| `configs/edge.yaml` | API-2 |
| `scripts/e2e-openai-ollama.sh` | API-2 |
| `apps/edge/README.md` | API-2 |

## 최종 검증

```bash
go test ./packages/go/config ./apps/edge/internal/openai
./scripts/e2e-openai-ollama.sh
```

Go test cache output is acceptable for unchanged packages; if implementation changes test-sensitive logic and fresh execution is needed, use `go test -count=1 ./packages/go/config ./apps/edge/internal/openai` and record the substitution.

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