<!-- task=14_e2e_temp_workspace plan=0 tag=E2EDB -->

# Plan - E2EDB

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

**필수: 구현 마지막에는 반드시 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 명령 출력으로 채운다.** 이 task는 E2E smoke가 repo root에 `iop.db`를 쓰는 storage artifact 문제를 고친다. 구현 체크리스트와 review stub의 구현 체크리스트를 모두 완료하고, 중간/최종 검증 출력은 실제 stdout/stderr로 남긴다. review 파일의 `이 파일을 읽는 리뷰 에이전트에게` 아카이브 지시와 `코드리뷰 전용 체크리스트`는 코드리뷰 skill 전용이므로 구현 에이전트가 실행하거나 수정하지 않는다.

## 배경

`make test-e2e`의 temp Edge config가 node `runtime.workspace_root`를 설정하지 않아 node bootstrap이 fallback DSN `file:iop.db?cache=shared&mode=rwc`를 사용한다. 이 때문에 smoke test가 repo root의 `iop.db`를 만들거나 갱신한다. 테스트 산출물은 `$TMP_DIR` 안에 격리되어야 하므로 smoke config와 검증을 보강한다.

## 의존 관계 및 구현 순서

- 선행 task 없음.
- `agent-task/14+ops_command_smoke`는 같은 `scripts/e2e-smoke.sh`를 수정하므로 이 task가 `complete.log`를 만든 뒤 시작한다.
- `agent-task/13_transport_idle_heartbeat`, `agent-task/15_node_command_docs_accuracy`와 병렬 진행 가능하다.

## 분석 결과

### 읽은 파일

- `scripts/e2e-smoke.sh`
- `apps/node/internal/bootstrap/module.go`
- `apps/node/internal/bootstrap/module_test.go`
- `Makefile`

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

- E2E temp config의 workspace root 설정: 기존 test는 node store DSN fallback 자체는 검증하지만 smoke script가 fallback을 쓰는지 확인하지 않는다. 공백 있음.
- repo root `iop.db` mtime/생성 방지: 기존 검증 없음. 이번 script 또는 final command에서 확인해야 한다.

### 심볼 참조

- renamed/removed symbol 없음.
- shell function 추가 시 call site는 같은 `scripts/e2e-smoke.sh` 내부로 제한한다.

### 범위 결정 근거

- `apps/node/internal/bootstrap/module.go:107-114`의 fallback 동작은 유지한다. fallback은 일반 실행의 기본값이고, 이번 문제는 smoke config가 workspace를 주지 않는 것이다.
- `.gitignore`에 `iop.db`를 추가해 숨기는 방식은 제외한다. 실제 side effect를 없애는 것이 목표다.
- 기본 `configs/edge.yaml`은 수정하지 않는다.

### 빌드 등급

- Build `cloud-G07`: shell/bin smoke orchestration과 storage side effect 검증이 포함된다.
- Review `cloud-G07`: repo root artifact가 실제로 더 이상 갱신되지 않는지 재실행 확인이 필요하다.

## 구현 체크리스트

- [ ] [E2EDB-1] E2E smoke temp Edge config의 node runtime workspace를 `$TMP_DIR/workspace`로 설정한다.
- [ ] [E2EDB-2] smoke script 또는 검증 계약에 root `iop.db` fallback 사용 방지 assertion을 추가한다.
- [ ] 중간 검증과 최종 검증 명령을 모두 실행하고 실제 stdout/stderr를 CODE_REVIEW에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [E2EDB-1] temp Edge config workspace_root 설정

#### 문제

`scripts/e2e-smoke.sh:30-51`과 `scripts/e2e-smoke.sh:87-103`이 생성하는 Edge config에는 node `runtime.workspace_root`가 없다. Node bootstrap fallback은 `apps/node/internal/bootstrap/module.go:107-110`에서 빈 workspace root일 때 repo current working directory 기준 `file:iop.db?cache=shared&mode=rwc`를 반환한다.

Before:

```yaml
# scripts/e2e-smoke.sh:33
nodes:
  - id: test-node
    alias: test-node
    token: test-token
    adapters:
```

After:

```yaml
nodes:
  - id: test-node
    alias: test-node
    token: test-token
    runtime:
      workspace_root: "$TMP_DIR/workspace"
    adapters:
```

#### 해결 방법

- mock profile heredoc과 real profile heredoc 양쪽에 동일한 `runtime.workspace_root`를 추가한다.
- path는 repo-relative가 아니라 absolute temp path인 `$TMP_DIR/workspace`를 사용한다.
- 별도 `mkdir`은 필수 아님. `storeDSN`이 `os.MkdirAll(workspaceRoot, 0o755)`를 수행한다.

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

- [ ] `scripts/e2e-smoke.sh` mock Edge config heredoc 수정
- [ ] `scripts/e2e-smoke.sh` real profile Edge config heredoc 수정
- [ ] YAML indentation이 기존 node fields와 맞는지 확인

#### 테스트 작성

- shell script change라 Go unit test는 추가하지 않는다.
- existing `IOP_E2E_IDLE_SECONDS=2 make test-e2e`가 generated config를 실제 사용한다.

#### 중간 검증

```bash
bash -n scripts/e2e-smoke.sh
IOP_E2E_IDLE_SECONDS=2 make test-e2e
```

Expected: syntax PASS, mock smoke PASS. Node log는 fallback `file:iop.db?cache=shared&mode=rwc`를 쓰지 않아야 한다.

### [E2EDB-2] root iop.db side effect assertion 추가

#### 문제

현재 `scripts/e2e-smoke.sh:222-227`의 assertions는 node registration, run start/complete, terminate만 확인한다. store DSN이 repo root fallback인지 여부는 확인하지 않아 root `iop.db` 오염이 재발해도 smoke가 PASS할 수 있다.

Before:

```bash
# scripts/e2e-smoke.sh:222
check_grep "test-node" "$EDGE_OUT" "node registration not found"
check_grep "start run_id=" "$EDGE_OUT" "run start not found"
check_grep "complete run_id=" "$EDGE_OUT" "run completion not found"
```

After:

```bash
check_grep "test-node" "$EDGE_OUT" "node registration not found"
check_grep "start run_id=" "$EDGE_OUT" "run start not found"
check_grep "complete run_id=" "$EDGE_OUT" "run completion not found"
check_no_grep 'file:iop.db?cache=shared&mode=rwc' "$NODE_OUT" "node store used repo-root fallback dsn"
```

#### 해결 방법

- `check_no_grep` helper를 추가한다.
- `$NODE_OUT`에 fallback DSN 문자열이 있으면 FAIL 처리한다.
- exact temp path grep은 log encoder 차이에 취약하므로 fallback absent assertion을 우선한다.

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

- [ ] `scripts/e2e-smoke.sh`에 `check_no_grep` helper 추가
- [ ] `$NODE_OUT` fallback DSN absent assertion 추가
- [ ] failure output이 원인 문자열을 보여주도록 grep 결과 출력

#### 테스트 작성

- shell smoke 검증으로 대체한다. 별도 Go test는 storage fallback 자체가 이미 `apps/node/internal/bootstrap/module_test.go`에서 다뤄지므로 추가하지 않는다.

#### 중간 검증

```bash
before=$(stat -c %Y iop.db 2>/dev/null || echo missing)
IOP_E2E_IDLE_SECONDS=2 make test-e2e
after=$(stat -c %Y iop.db 2>/dev/null || echo missing)
test "$before" = "$after"
```

Expected: smoke PASS, root `iop.db` mtime 또는 존재 상태가 변하지 않는다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `scripts/e2e-smoke.sh` | E2EDB-1, E2EDB-2 |
| `agent-task/14_e2e_temp_workspace/CODE_REVIEW-cloud-G07.md` | evidence 기록 |

## 최종 검증

```bash
bash -n scripts/e2e-smoke.sh
go test -count=1 ./apps/node/internal/bootstrap
before=$(stat -c %Y iop.db 2>/dev/null || echo missing)
IOP_E2E_IDLE_SECONDS=2 make test-e2e
after=$(stat -c %Y iop.db 2>/dev/null || echo missing)
test "$before" = "$after"
git diff --check
```

Expected: all commands exit 0. Go test cache output은 `-count=1` 때문에 허용하지 않는다. `before`와 `after`가 같아야 하며, root `iop.db`가 없던 경우 둘 다 `missing`이어야 한다.

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