<!-- task=m-openai-workspace-agent-execution-contract/06+05_authoring_http_smoke plan=0 tag=AUTHORING_SMOKE -->

# Plan - AUTHORING_SMOKE

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

`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 필수 마지막 단계다. 검증을 실행하고 실제 출력과 구현 메모를 기록한 뒤 active 파일을 그대로 두고 리뷰 준비 상태로 보고한다. 사용자 결정, 사용자 소유 외부 환경, 또는 범위 충돌 없이는 진행할 수 없을 때만 review stub의 `사용자 리뷰 요청` 섹션을 증거와 함께 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰 요청이 아니다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.

## 배경

`nomad-http-smoke`의 핵심은 NomadCode가 IOP Edge HTTP 표면만 호출해 workspace checkout에 파일 변경을 남길 수 있는지 확인하는 것이다. 현재 `iop-edge smoke openai`는 `metadata.task_id`와 `metadata.source`만 보내고 workspace를 넣지 않는다. 기존 `scripts/e2e-openai-ollama.sh`도 Ollama route만 검증하므로 CLI workspace-bound authoring route를 별도 deterministic smoke로 증명해야 한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식을 따른다. 구현 에이전트는 직접 사용자 프롬프트를 만들지 않고, code-review가 사용자 리뷰 필요성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/openai-workspace-agent-execution-contract.md`
- Task ids:
  - `workspace-authoring-smoke`: `metadata.workspace`가 가리키는 임시 checkout에서 workspace-bound agent target이 파일을 생성/수정할 수 있음을 검증한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/openai-workspace-agent-execution-contract.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/testing-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/project/e2e-smoke/SKILL.md`
- `apps/edge/internal/edgecmd/smoke_openai.go`
- `apps/edge/cmd/edge/main_test.go`
- `apps/edge/internal/openai/responses_handler.go`
- `apps/edge/internal/openai/server_test.go`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/run_dispatch.go`
- `apps/node/internal/adapters/cli/cli.go`
- `apps/node/internal/adapters/cli/oneshot.go`
- `apps/node/internal/adapters/cli/persistent.go`
- `apps/node/internal/adapters/cli/cli_internal_test.go`
- `scripts/e2e-openai-ollama.sh`
- `configs/edge.yaml`

### 테스트 환경 규칙

- test_env: `local`.
- `agent-test/local/rules.md`를 읽었다. 현재 checkout의 quick check를 우선하고, Docker-in-Docker는 쓰지 않는다.
- matched profiles: `agent-test/local/edge-smoke.md`, `agent-test/local/node-smoke.md`, `agent-test/local/testing-smoke.md`, `agent-test/local/platform-common-smoke.md`.
- 적용 명령: edge 변경은 `go test ./apps/edge/...`, node 변경은 `go test ./apps/node/...`, 테스트 도구 변경은 해당 도구 직접 실행, OpenAI-compatible 경계 변경은 `iop-edge smoke openai` 또는 동등한 `/healthz`, `/v1/models`, `/v1/responses` 확인.
- 이 plan은 새 deterministic CLI workspace HTTP smoke를 직접 실행한다. Go test cache가 실패를 숨기지 않도록 `-count=1`을 쓴다.
- `<확인 필요>` 값은 없었다.

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

- `apps/edge/cmd/edge/main_test.go:1043-1087`은 smoke command success를 검증하지만 request body에 workspace가 들어가는지는 검증하지 않는다.
- `apps/edge/internal/edgecmd/smoke_openai.go:203-216`은 responses metadata에 workspace를 보내지 않는다.
- `scripts/e2e-openai-ollama.sh:203-207`은 Ollama route만 검증하고 workspace-bound CLI route를 만들지 않는다.
- `configs/edge.yaml:59-64`는 `workspace_required: true` 예시를 갖고 있지만 이를 실행하는 smoke가 없다.
- 이 plan에서 command-level body assertion과 실제 marker-file smoke를 추가한다.

### 심볼 참조

- renamed/removed symbols: none.
- 추가 flag는 `smokeOpenAICmd` local vars와 Cobra flag binding에만 닿는다.

### 분할 판단

- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `m-openai-workspace-agent-execution-contract`.
- sibling subtasks:
  - `05_failure_surface`: predecessor. workspace 실패 표면을 먼저 정리한다.
  - `06+05_authoring_http_smoke`: 이 plan. `05`의 `complete.log`가 필요하다.
  - `07_nomad_metadata_shape`: 독립. sibling repo metadata producer 정렬이다.
- predecessor `05`: active `agent-task/m-openai-workspace-agent-execution-contract/05_failure_surface/complete.log` 없음, archive candidate 없음. 구현은 `05_failure_surface` PASS 후 시작해야 한다.

### 범위 결정 근거

- 기존 Ollama smoke는 유지한다. CLI workspace smoke는 behavior와 fixture가 달라 기존 script를 과도하게 복잡하게 만들지 않는다.
- 실제 Codex/OpenCode provider 계정에 의존하지 않고 deterministic local `sh` CLI profile로 검증한다.
- NomadCode producer 변경은 `07_nomad_metadata_shape`에서 처리한다.
- failure error classification은 `05_failure_surface` 범위다.

### 빌드 등급

- `cloud-G07`: shell/CLI workflow, bin script orchestration, process control, HTTP smoke evidence가 중심이며 실제 runtime 증거 신뢰가 중요하다.

## 구현 체크리스트

- [ ] [AUTHORING_SMOKE-1] `iop-edge smoke openai`에 workspace-aware request 옵션과 request-body regression test를 추가한다.
- [ ] [AUTHORING_SMOKE-2] deterministic CLI workspace authoring e2e smoke를 추가해 marker가 workspace 안에만 생성되는지 검증한다.
- [ ] `go test -count=1 ./apps/edge/cmd/edge ./apps/edge/internal/openai ./apps/node/internal/adapters/cli`와 새 e2e smoke script를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

이 subtask directory의 `06+05`가 runtime source of truth다. 구현 전에 `agent-task/m-openai-workspace-agent-execution-contract/05_failure_surface/complete.log` 또는 matching archive `05_*`/`05+*` `complete.log`가 있어야 한다. 현재 plan 작성 시점에는 없다.

### [AUTHORING_SMOKE-1] Smoke Command Workspace Metadata

#### 문제

`iop-edge smoke openai`는 responses payload에 task/source metadata는 넣지만 workspace는 넣지 않는다.

```go
// apps/edge/internal/edgecmd/smoke_openai.go:34-39
var (
	smokeModel   string
	smokeBaseURL string
	smokePrompt  string
	smokeTimeout string
)
```

```go
// apps/edge/internal/edgecmd/smoke_openai.go:203-216
responsesPayload := map[string]interface{}{
	"model":  smokeModel,
	"input":  prompt,
	"stream": false,
	"metadata": map[string]interface{}{
		"request_id": "iop-edge-smoke",
		"task_id":    "smoke",
		"source":     "iop-edge-smoke",
		"inference": map[string]string{
			"target": smokeModel,
		},
	},
}
```

#### 해결 방법

`--workspace` flag를 추가한다. 값이 비어 있으면 기존 payload를 유지하고, 값이 있으면 `metadata.workspace`를 flat string으로 넣는다. 추가로 `--expect-file`과 `--expect-contains`를 넣어 command가 smoke 후 workspace 안 파일을 검사할 수 있게 한다.

```go
// after
if smokeWorkspace != "" {
	metadata["workspace"] = smokeWorkspace
}
// after successful /v1/responses
if smokeExpectFile != "" {
	path := filepath.Join(smokeWorkspace, smokeExpectFile)
	// workspace 안 상대 경로만 허용하고 파일 내용 검사
}
```

`apps/edge/cmd/edge/main_test.go`의 httptest server에서 `/v1/responses` body를 decode해 workspace가 flag 값과 같은지 확인한다.

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

- [ ] `apps/edge/internal/edgecmd/smoke_openai.go`: `--workspace`, `--expect-file`, `--expect-contains` flag와 metadata/file 검증 추가.
- [ ] `apps/edge/cmd/edge/main_test.go`: request body assertion test 추가.

#### 테스트 작성

테스트 작성: yes. `TestSmokeOpenAICommandSuccess`를 확장하거나 새 test를 추가해 workspace flag가 `metadata.workspace` flat string으로 전송되는지 검증한다. `--expect-file`은 temp workspace에 파일을 미리 만들거나 httptest 응답 후 검사하는 방식으로 정상/실패 boundary를 검증한다.

#### 중간 검증

```bash
go test -count=1 ./apps/edge/cmd/edge
```

기대 결과: smoke command tests가 모두 통과한다.

### [AUTHORING_SMOKE-2] CLI Workspace E2E Script

#### 문제

`scripts/e2e-openai-ollama.sh:130-136`은 model route를 Ollama adapter로만 구성한다. `scripts/e2e-openai-ollama.sh:221-225`의 command smoke도 workspace를 넘기지 않는다.

```bash
# scripts/e2e-openai-ollama.sh:130-136
  model_routes:
    - model: "client-request-model"
      adapter: "ollama"
      target: "$MODEL"
    - model: "$MODEL"
      adapter: "ollama"
      target: "$MODEL"
```

#### 해결 방법

새 script `scripts/e2e-openai-cli-workspace.sh`를 추가한다. 기존 Ollama script는 그대로 두고, temp workspace와 temp Edge/Node config를 만든 뒤 CLI profile을 deterministic `sh -lc` command로 구성한다. `/v1/responses` 또는 `iop-edge smoke openai --workspace`를 호출하면 profile이 cwd에 marker file을 쓰고 stdout에 token을 출력해야 한다.

검증 조건:

- `metadata.workspace`는 absolute temp workspace path다.
- route는 `workspace_required: true`인 CLI adapter route다.
- marker file은 workspace 안에 생성된다.
- repo root와 temp parent에는 marker가 없어야 한다.
- `git -C "$WORKSPACE" status --short` 또는 marker content로 변경을 확인한다.

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

- [ ] `scripts/e2e-openai-cli-workspace.sh`: 새 deterministic smoke script 추가.
- [ ] `scripts/e2e-openai-ollama.sh`: 수정하지 않는다. 중복 공통화가 명확히 작아지는 경우에만 helper 추출을 고려한다.
- [ ] `configs/edge.yaml`: 기본 예시가 충분하면 수정하지 않는다.

#### 테스트 작성

테스트 작성: yes. 이 item 자체가 smoke test 추가다. 별도 Go unit은 AUTHORING_SMOKE-1에서 command payload를 커버한다.

#### 중간 검증

```bash
./scripts/e2e-openai-cli-workspace.sh
```

기대 결과: `/healthz`, `/v1/models`, `/v1/responses` 또는 `iop-edge smoke openai`가 통과하고 marker file이 workspace 안에만 있다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/edgecmd/smoke_openai.go` | AUTHORING_SMOKE-1 |
| `apps/edge/cmd/edge/main_test.go` | AUTHORING_SMOKE-1 |
| `scripts/e2e-openai-cli-workspace.sh` | AUTHORING_SMOKE-2 |
| `scripts/e2e-openai-ollama.sh` | 읽기/회귀 대상, 수정 금지 기본값 |
| `configs/edge.yaml` | 예시 부족 발견 시만 AUTHORING_SMOKE-2 |

## 최종 검증

```bash
go test -count=1 ./apps/edge/cmd/edge ./apps/edge/internal/openai ./apps/node/internal/adapters/cli
./scripts/e2e-openai-cli-workspace.sh
./scripts/e2e-openai-ollama.sh
git diff --check
```

기대 결과: 모든 명령이 0 exit. Go test cache output은 허용하지 않는다. `git diff --check` output은 없어야 한다.

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